diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index c6c1e42ae52..8f416e09511 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -163,9 +163,6 @@ apps/desktop/desktop_native/windows_plugin_authenticator @bitwarden/team-autofil apps/desktop/desktop_native/autotype @bitwarden/team-autofill-desktop-dev apps/desktop/desktop_native/core/src/ssh_agent @bitwarden/team-autofill-desktop-dev @bitwarden/wg-ssh-keys apps/desktop/desktop_native/ssh_agent @bitwarden/team-autofill-desktop-dev @bitwarden/wg-ssh-keys -apps/desktop/desktop_native/napi/src/autofill.rs @bitwarden/team-autofill-desktop-dev -apps/desktop/desktop_native/napi/src/autotype.rs @bitwarden/team-autofill-desktop-dev -apps/desktop/desktop_native/napi/src/sshagent.rs @bitwarden/team-autofill-desktop-dev # DuckDuckGo integration apps/desktop/native-messaging-test-runner @bitwarden/team-autofill-desktop-dev apps/desktop/src/services/duckduckgo-message-handler.service.ts @bitwarden/team-autofill-desktop-dev @@ -192,6 +189,7 @@ apps/cli/src/key-management @bitwarden/team-key-management-dev bitwarden_license/bit-web/src/app/key-management @bitwarden/team-key-management-dev libs/key-management @bitwarden/team-key-management-dev libs/key-management-ui @bitwarden/team-key-management-dev +libs/user-crypto-management @bitwarden/team-key-management-dev libs/common/src/key-management @bitwarden/team-key-management-dev # Node-cryptofunction service libs/node @bitwarden/team-key-management-dev diff --git a/.github/renovate.json5 b/.github/renovate.json5 index b264514e736..1e8f72cd8a0 100644 --- a/.github/renovate.json5 +++ b/.github/renovate.json5 @@ -542,6 +542,12 @@ }, // ==================== Special Version Constraints ==================== + { + matchPackageNames: ["actions/create-github-app-token"], + matchFileNames: [".github/workflows/test-browser-interactions.yml"], + allowedVersions: "<= 2.0.3", + description: "Versions after v2.0.3 break the test-browser-interactions workflow. Remediation tracked in PM-28174.", + }, { // Any versions of lowdb above 1.0.0 are not compatible with CommonJS. matchPackageNames: ["lowdb"], diff --git a/.github/workflows/release-web.yml b/.github/workflows/release-web.yml index 8fb9e2487e1..f6feb3386a7 100644 --- a/.github/workflows/release-web.yml +++ b/.github/workflows/release-web.yml @@ -97,3 +97,4 @@ jobs: artifacts: "apps/web/artifacts/web-${{ needs.setup.outputs.release_version }}-selfhosted-COMMERCIAL.zip, apps/web/artifacts/web-${{ needs.setup.outputs.release_version }}-selfhosted-open-source.zip" token: ${{ secrets.GITHUB_TOKEN }} + draft: true diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index c2fd4b7c32b..d3cd64bde11 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -101,7 +101,7 @@ jobs: echo "node_version=$NODE_VERSION" >> "$GITHUB_OUTPUT" - name: Set up Node - uses: actions/setup-node@a0853c24544627f65ddf259abe73b1d18a591444 # v5.0.0 + uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 # v6.2.0 with: cache: 'npm' cache-dependency-path: '**/package-lock.json' diff --git a/.storybook/tsconfig.json b/.storybook/tsconfig.json index 34acc9a740c..ab86c4179af 100644 --- a/.storybook/tsconfig.json +++ b/.storybook/tsconfig.json @@ -1,7 +1,8 @@ { "extends": "../tsconfig", "compilerOptions": { - "allowSyntheticDefaultImports": true + "allowSyntheticDefaultImports": true, + "moduleResolution": "bundler" }, "exclude": ["../src/test.setup.ts", "../apps/**/*.spec.ts", "../libs/**/*.spec.ts"], "files": [ diff --git a/apps/browser/package.json b/apps/browser/package.json index 53103643374..fa3da23991e 100644 --- a/apps/browser/package.json +++ b/apps/browser/package.json @@ -1,6 +1,6 @@ { "name": "@bitwarden/browser", - "version": "2026.1.1", + "version": "2026.2.0", "scripts": { "build": "npm run build:chrome", "build:bit": "npm run build:bit:chrome", diff --git a/apps/browser/src/_locales/ar/messages.json b/apps/browser/src/_locales/ar/messages.json index 7334362d446..5768966511d 100644 --- a/apps/browser/src/_locales/ar/messages.json +++ b/apps/browser/src/_locales/ar/messages.json @@ -896,6 +896,9 @@ "invalidVerificationCode": { "message": "رمز التحقق غير صالح" }, + "invalidEmailOrVerificationCode": { + "message": "Invalid email or verification code" + }, "valueCopied": { "message": "تم نسخ $VALUE$", "description": "Value has been copied to the clipboard.", @@ -2857,6 +2860,9 @@ "reviewAtRiskLoginSlideImgAltPeriod": { "message": "Illustration of a list of logins that are at-risk." }, + "welcomeDialogGraphicAlt": { + "message": "Illustration of the layout of the Bitwarden vault page." + }, "generatePasswordSlideDesc": { "message": "Quickly generate a strong, unique password with the Bitwarden autofill menu on the at-risk site.", "description": "Description of the generate password slide on the at-risk password page carousel" @@ -3080,6 +3086,45 @@ } } }, + "durationTimeHours": { + "message": "$HOURS$ hours", + "placeholders": { + "hours": { + "content": "$1", + "example": "5" + } + } + }, + "sendCreatedDescriptionV2": { + "message": "Copy and share this Send link. The Send will be available to anyone with the link for the next $TIME$.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated.", + "placeholders": { + "time": { + "content": "$1", + "example": "7 days, 1 hour, 1 day" + } + } + }, + "sendCreatedDescriptionPassword": { + "message": "Copy and share this Send link. The Send will be available to anyone with the link and password you set for the next $TIME$.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated.", + "placeholders": { + "time": { + "content": "$1", + "example": "7 days, 1 hour, 1 day" + } + } + }, + "sendCreatedDescriptionEmail": { + "message": "Copy and share this Send link. It can be viewed by the people you specified for the next $TIME$.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated.", + "placeholders": { + "time": { + "content": "$1", + "example": "7 days, 1 hour, 1 day" + } + } + }, "sendLinkCopied": { "message": "إرسال رابط منسوخ", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." @@ -6121,6 +6166,9 @@ "enterMultipleEmailsSeparatedByComma": { "message": "Enter multiple emails by separating with a comma." }, + "emailsRequiredChangeAccessType": { + "message": "Email verification requires at least one email address. To remove all emails, change the access type above." + }, "emailPlaceholder": { "message": "user@bitwarden.com , user@acme.com" }, @@ -6133,5 +6181,8 @@ "sendPasswordHelperText": { "message": "Individuals will need to enter the password to view this Send", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "userVerificationFailed": { + "message": "User verification failed." } } diff --git a/apps/browser/src/_locales/az/messages.json b/apps/browser/src/_locales/az/messages.json index 6168f7cf2dd..eb3135599f4 100644 --- a/apps/browser/src/_locales/az/messages.json +++ b/apps/browser/src/_locales/az/messages.json @@ -896,6 +896,9 @@ "invalidVerificationCode": { "message": "Yararsız doğrulama kodu" }, + "invalidEmailOrVerificationCode": { + "message": "Invalid email or verification code" + }, "valueCopied": { "message": "$VALUE$ kopyalandı", "description": "Value has been copied to the clipboard.", @@ -2857,6 +2860,9 @@ "reviewAtRiskLoginSlideImgAltPeriod": { "message": "Risk altındakı girişlərin olduğu siyahının təsviri." }, + "welcomeDialogGraphicAlt": { + "message": "Illustration of the layout of the Bitwarden vault page." + }, "generatePasswordSlideDesc": { "message": "Risk altında olan saytda Bitwarden avto-doldurma menyusu ilə güclü, unikal parolları cəld yaradın.", "description": "Description of the generate password slide on the at-risk password page carousel" @@ -3080,6 +3086,45 @@ } } }, + "durationTimeHours": { + "message": "$HOURS$ saat", + "placeholders": { + "hours": { + "content": "$1", + "example": "5" + } + } + }, + "sendCreatedDescriptionV2": { + "message": "Bu Send keçidini kopyala və paylaş. Send, keçidə sahib olan hər kəs üçün növbəti $TIME$ ərzində əlçatan olacaq.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated.", + "placeholders": { + "time": { + "content": "$1", + "example": "7 days, 1 hour, 1 day" + } + } + }, + "sendCreatedDescriptionPassword": { + "message": "Bu Send keçidini kopyala və paylaş. Send, keçidə və ayarladığınız parola sahib olan hər kəs üçün növbəti $TIME$ ərzində əlçatan olacaq.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated.", + "placeholders": { + "time": { + "content": "$1", + "example": "7 days, 1 hour, 1 day" + } + } + }, + "sendCreatedDescriptionEmail": { + "message": "Bu Send keçidini kopyala və paylaş. Qeyd etdiyiniz şəxslər buna növbəti $TIME$ ərzində baxa bilər.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated.", + "placeholders": { + "time": { + "content": "$1", + "example": "7 days, 1 hour, 1 day" + } + } + }, "sendLinkCopied": { "message": "Send keçidi kopyalandı", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." @@ -6121,6 +6166,9 @@ "enterMultipleEmailsSeparatedByComma": { "message": "Birdən çox e-poçtu daxil edərkən vergül istifadə edin." }, + "emailsRequiredChangeAccessType": { + "message": "Email verification requires at least one email address. To remove all emails, change the access type above." + }, "emailPlaceholder": { "message": "user@bitwarden.com , user@acme.com" }, @@ -6133,5 +6181,8 @@ "sendPasswordHelperText": { "message": "Şəxslər, Send-ə baxması üçün parolu daxil etməli olacaqlar", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "userVerificationFailed": { + "message": "User verification failed." } } diff --git a/apps/browser/src/_locales/be/messages.json b/apps/browser/src/_locales/be/messages.json index ea569cabdf4..cd48ff09ee4 100644 --- a/apps/browser/src/_locales/be/messages.json +++ b/apps/browser/src/_locales/be/messages.json @@ -896,6 +896,9 @@ "invalidVerificationCode": { "message": "Памылковы праверачны код" }, + "invalidEmailOrVerificationCode": { + "message": "Invalid email or verification code" + }, "valueCopied": { "message": "$VALUE$ скапіяваны", "description": "Value has been copied to the clipboard.", @@ -2857,6 +2860,9 @@ "reviewAtRiskLoginSlideImgAltPeriod": { "message": "Illustration of a list of logins that are at-risk." }, + "welcomeDialogGraphicAlt": { + "message": "Illustration of the layout of the Bitwarden vault page." + }, "generatePasswordSlideDesc": { "message": "Quickly generate a strong, unique password with the Bitwarden autofill menu on the at-risk site.", "description": "Description of the generate password slide on the at-risk password page carousel" @@ -3080,6 +3086,45 @@ } } }, + "durationTimeHours": { + "message": "$HOURS$ hours", + "placeholders": { + "hours": { + "content": "$1", + "example": "5" + } + } + }, + "sendCreatedDescriptionV2": { + "message": "Copy and share this Send link. The Send will be available to anyone with the link for the next $TIME$.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated.", + "placeholders": { + "time": { + "content": "$1", + "example": "7 days, 1 hour, 1 day" + } + } + }, + "sendCreatedDescriptionPassword": { + "message": "Copy and share this Send link. The Send will be available to anyone with the link and password you set for the next $TIME$.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated.", + "placeholders": { + "time": { + "content": "$1", + "example": "7 days, 1 hour, 1 day" + } + } + }, + "sendCreatedDescriptionEmail": { + "message": "Copy and share this Send link. It can be viewed by the people you specified for the next $TIME$.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated.", + "placeholders": { + "time": { + "content": "$1", + "example": "7 days, 1 hour, 1 day" + } + } + }, "sendLinkCopied": { "message": "Send link copied", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." @@ -6121,6 +6166,9 @@ "enterMultipleEmailsSeparatedByComma": { "message": "Enter multiple emails by separating with a comma." }, + "emailsRequiredChangeAccessType": { + "message": "Email verification requires at least one email address. To remove all emails, change the access type above." + }, "emailPlaceholder": { "message": "user@bitwarden.com , user@acme.com" }, @@ -6133,5 +6181,8 @@ "sendPasswordHelperText": { "message": "Individuals will need to enter the password to view this Send", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "userVerificationFailed": { + "message": "User verification failed." } } diff --git a/apps/browser/src/_locales/bg/messages.json b/apps/browser/src/_locales/bg/messages.json index e5d68bce366..275dd21d0e9 100644 --- a/apps/browser/src/_locales/bg/messages.json +++ b/apps/browser/src/_locales/bg/messages.json @@ -896,6 +896,9 @@ "invalidVerificationCode": { "message": "Грешен код за потвърждаване" }, + "invalidEmailOrVerificationCode": { + "message": "Грешна е-поща или код за потвърждаване" + }, "valueCopied": { "message": "Копирано е $VALUE$", "description": "Value has been copied to the clipboard.", @@ -2857,6 +2860,9 @@ "reviewAtRiskLoginSlideImgAltPeriod": { "message": "Илюстрация на списък с елементи за вписване, които са в риск." }, + "welcomeDialogGraphicAlt": { + "message": "Илюстрация на оформлението на страницата с трезора в Битуорден." + }, "generatePasswordSlideDesc": { "message": "Генерирайте бързо сложна и уникална парола от менюто за автоматично попълване на Битуорден, на уеб сайта, който е в риск.", "description": "Description of the generate password slide on the at-risk password page carousel" @@ -3080,6 +3086,45 @@ } } }, + "durationTimeHours": { + "message": "$HOURS$ часа", + "placeholders": { + "hours": { + "content": "$1", + "example": "5" + } + } + }, + "sendCreatedDescriptionV2": { + "message": "Копирайте и споделете връзката към Изпращането. То ще бъде достъпно за всеки с връзката в рамките на следващите $TIME$.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated.", + "placeholders": { + "time": { + "content": "$1", + "example": "7 days, 1 hour, 1 day" + } + } + }, + "sendCreatedDescriptionPassword": { + "message": "Копирайте и споделете връзката към Изпращането. То ще бъде достъпно за всеки с връзката и паролата, която зададете, в рамките на следващите $TIME$.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated.", + "placeholders": { + "time": { + "content": "$1", + "example": "7 days, 1 hour, 1 day" + } + } + }, + "sendCreatedDescriptionEmail": { + "message": "Копирайте и споделете връзка към това Изпращане. То ще може да бъде видяно само от хората, които сте посочили, в рамките на следващите $TIME$.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated.", + "placeholders": { + "time": { + "content": "$1", + "example": "7 days, 1 hour, 1 day" + } + } + }, "sendLinkCopied": { "message": "Връзката към Изпращането е копирана", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." @@ -6121,6 +6166,9 @@ "enterMultipleEmailsSeparatedByComma": { "message": "Можете да въведете повече е-пощи, като ги разделите със запетая." }, + "emailsRequiredChangeAccessType": { + "message": "Потвърждаването на е-пощата изисква да е наличен поне един адрес на е-поща. Ако искате да премахнете всички е-пощи, променете начина за достъп по-горе." + }, "emailPlaceholder": { "message": "потребител@bitwarden.com , потребител@acme.com" }, @@ -6133,5 +6181,8 @@ "sendPasswordHelperText": { "message": "Хората ще трябва да въведат паролата, за да видят това Изпращане", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "userVerificationFailed": { + "message": "Проверката на потребителя беше неуспешна." } } diff --git a/apps/browser/src/_locales/bn/messages.json b/apps/browser/src/_locales/bn/messages.json index 533b12ab0a5..8aa07e2ec82 100644 --- a/apps/browser/src/_locales/bn/messages.json +++ b/apps/browser/src/_locales/bn/messages.json @@ -896,6 +896,9 @@ "invalidVerificationCode": { "message": "Invalid verification code" }, + "invalidEmailOrVerificationCode": { + "message": "Invalid email or verification code" + }, "valueCopied": { "message": "$VALUE$ অনুলিপিত", "description": "Value has been copied to the clipboard.", @@ -2857,6 +2860,9 @@ "reviewAtRiskLoginSlideImgAltPeriod": { "message": "Illustration of a list of logins that are at-risk." }, + "welcomeDialogGraphicAlt": { + "message": "Illustration of the layout of the Bitwarden vault page." + }, "generatePasswordSlideDesc": { "message": "Quickly generate a strong, unique password with the Bitwarden autofill menu on the at-risk site.", "description": "Description of the generate password slide on the at-risk password page carousel" @@ -3080,6 +3086,45 @@ } } }, + "durationTimeHours": { + "message": "$HOURS$ hours", + "placeholders": { + "hours": { + "content": "$1", + "example": "5" + } + } + }, + "sendCreatedDescriptionV2": { + "message": "Copy and share this Send link. The Send will be available to anyone with the link for the next $TIME$.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated.", + "placeholders": { + "time": { + "content": "$1", + "example": "7 days, 1 hour, 1 day" + } + } + }, + "sendCreatedDescriptionPassword": { + "message": "Copy and share this Send link. The Send will be available to anyone with the link and password you set for the next $TIME$.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated.", + "placeholders": { + "time": { + "content": "$1", + "example": "7 days, 1 hour, 1 day" + } + } + }, + "sendCreatedDescriptionEmail": { + "message": "Copy and share this Send link. It can be viewed by the people you specified for the next $TIME$.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated.", + "placeholders": { + "time": { + "content": "$1", + "example": "7 days, 1 hour, 1 day" + } + } + }, "sendLinkCopied": { "message": "Send link copied", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." @@ -6121,6 +6166,9 @@ "enterMultipleEmailsSeparatedByComma": { "message": "Enter multiple emails by separating with a comma." }, + "emailsRequiredChangeAccessType": { + "message": "Email verification requires at least one email address. To remove all emails, change the access type above." + }, "emailPlaceholder": { "message": "user@bitwarden.com , user@acme.com" }, @@ -6133,5 +6181,8 @@ "sendPasswordHelperText": { "message": "Individuals will need to enter the password to view this Send", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "userVerificationFailed": { + "message": "User verification failed." } } diff --git a/apps/browser/src/_locales/bs/messages.json b/apps/browser/src/_locales/bs/messages.json index 35c4177e5eb..8ef61e0e63e 100644 --- a/apps/browser/src/_locales/bs/messages.json +++ b/apps/browser/src/_locales/bs/messages.json @@ -896,6 +896,9 @@ "invalidVerificationCode": { "message": "Invalid verification code" }, + "invalidEmailOrVerificationCode": { + "message": "Invalid email or verification code" + }, "valueCopied": { "message": "$VALUE$ copied", "description": "Value has been copied to the clipboard.", @@ -2857,6 +2860,9 @@ "reviewAtRiskLoginSlideImgAltPeriod": { "message": "Illustration of a list of logins that are at-risk." }, + "welcomeDialogGraphicAlt": { + "message": "Illustration of the layout of the Bitwarden vault page." + }, "generatePasswordSlideDesc": { "message": "Quickly generate a strong, unique password with the Bitwarden autofill menu on the at-risk site.", "description": "Description of the generate password slide on the at-risk password page carousel" @@ -3080,6 +3086,45 @@ } } }, + "durationTimeHours": { + "message": "$HOURS$ hours", + "placeholders": { + "hours": { + "content": "$1", + "example": "5" + } + } + }, + "sendCreatedDescriptionV2": { + "message": "Copy and share this Send link. The Send will be available to anyone with the link for the next $TIME$.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated.", + "placeholders": { + "time": { + "content": "$1", + "example": "7 days, 1 hour, 1 day" + } + } + }, + "sendCreatedDescriptionPassword": { + "message": "Copy and share this Send link. The Send will be available to anyone with the link and password you set for the next $TIME$.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated.", + "placeholders": { + "time": { + "content": "$1", + "example": "7 days, 1 hour, 1 day" + } + } + }, + "sendCreatedDescriptionEmail": { + "message": "Copy and share this Send link. It can be viewed by the people you specified for the next $TIME$.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated.", + "placeholders": { + "time": { + "content": "$1", + "example": "7 days, 1 hour, 1 day" + } + } + }, "sendLinkCopied": { "message": "Send link copied", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." @@ -6121,6 +6166,9 @@ "enterMultipleEmailsSeparatedByComma": { "message": "Enter multiple emails by separating with a comma." }, + "emailsRequiredChangeAccessType": { + "message": "Email verification requires at least one email address. To remove all emails, change the access type above." + }, "emailPlaceholder": { "message": "user@bitwarden.com , user@acme.com" }, @@ -6133,5 +6181,8 @@ "sendPasswordHelperText": { "message": "Individuals will need to enter the password to view this Send", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "userVerificationFailed": { + "message": "User verification failed." } } diff --git a/apps/browser/src/_locales/ca/messages.json b/apps/browser/src/_locales/ca/messages.json index 8e82fc34be4..d524731fb46 100644 --- a/apps/browser/src/_locales/ca/messages.json +++ b/apps/browser/src/_locales/ca/messages.json @@ -896,6 +896,9 @@ "invalidVerificationCode": { "message": "Codi de verificació no vàlid" }, + "invalidEmailOrVerificationCode": { + "message": "Invalid email or verification code" + }, "valueCopied": { "message": "S'ha copiat $VALUE$", "description": "Value has been copied to the clipboard.", @@ -2857,6 +2860,9 @@ "reviewAtRiskLoginSlideImgAltPeriod": { "message": "Illustration of a list of logins that are at-risk." }, + "welcomeDialogGraphicAlt": { + "message": "Illustration of the layout of the Bitwarden vault page." + }, "generatePasswordSlideDesc": { "message": "Quickly generate a strong, unique password with the Bitwarden autofill menu on the at-risk site.", "description": "Description of the generate password slide on the at-risk password page carousel" @@ -3080,6 +3086,45 @@ } } }, + "durationTimeHours": { + "message": "$HOURS$ hours", + "placeholders": { + "hours": { + "content": "$1", + "example": "5" + } + } + }, + "sendCreatedDescriptionV2": { + "message": "Copy and share this Send link. The Send will be available to anyone with the link for the next $TIME$.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated.", + "placeholders": { + "time": { + "content": "$1", + "example": "7 days, 1 hour, 1 day" + } + } + }, + "sendCreatedDescriptionPassword": { + "message": "Copy and share this Send link. The Send will be available to anyone with the link and password you set for the next $TIME$.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated.", + "placeholders": { + "time": { + "content": "$1", + "example": "7 days, 1 hour, 1 day" + } + } + }, + "sendCreatedDescriptionEmail": { + "message": "Copy and share this Send link. It can be viewed by the people you specified for the next $TIME$.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated.", + "placeholders": { + "time": { + "content": "$1", + "example": "7 days, 1 hour, 1 day" + } + } + }, "sendLinkCopied": { "message": "Send link copied", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." @@ -6121,6 +6166,9 @@ "enterMultipleEmailsSeparatedByComma": { "message": "Enter multiple emails by separating with a comma." }, + "emailsRequiredChangeAccessType": { + "message": "Email verification requires at least one email address. To remove all emails, change the access type above." + }, "emailPlaceholder": { "message": "user@bitwarden.com , user@acme.com" }, @@ -6133,5 +6181,8 @@ "sendPasswordHelperText": { "message": "Individuals will need to enter the password to view this Send", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "userVerificationFailed": { + "message": "User verification failed." } } diff --git a/apps/browser/src/_locales/cs/messages.json b/apps/browser/src/_locales/cs/messages.json index ed1b37134e1..efadf781fc8 100644 --- a/apps/browser/src/_locales/cs/messages.json +++ b/apps/browser/src/_locales/cs/messages.json @@ -896,6 +896,9 @@ "invalidVerificationCode": { "message": "Neplatný ověřovací kód" }, + "invalidEmailOrVerificationCode": { + "message": "Neplatný e-mail nebo ověřovací kód" + }, "valueCopied": { "message": "Zkopírováno: $VALUE$", "description": "Value has been copied to the clipboard.", @@ -1688,7 +1691,7 @@ "message": "Ověřovací aplikace" }, "authenticatorAppDescV2": { - "message": "Zadejte kód vygenerovaný ověřovací aplikací, jako je Autentikátor Bitwarden.", + "message": "Zadejte kód vygenerovaný ověřovací aplikací, jako je Bitwarden Authenticator.", "description": "'Bitwarden Authenticator' is a product name and should not be translated." }, "yubiKeyTitleV2": { @@ -2857,6 +2860,9 @@ "reviewAtRiskLoginSlideImgAltPeriod": { "message": "Ilustrace seznamu přihlášení, která jsou ohrožená." }, + "welcomeDialogGraphicAlt": { + "message": "Ilustrace rozložení stránky trezoru Bitwarden." + }, "generatePasswordSlideDesc": { "message": "Rychle vygeneruje silné, unikátní heslo s nabídkou automatického vyplňování Bitwarden na ohrožených stránkách.", "description": "Description of the generate password slide on the at-risk password page carousel" @@ -3080,6 +3086,45 @@ } } }, + "durationTimeHours": { + "message": "$HOURS$ hodin", + "placeholders": { + "hours": { + "content": "$1", + "example": "5" + } + } + }, + "sendCreatedDescriptionV2": { + "message": "Zkopírujte a sdílejte tento odkaz Send. Send bude k dispozici komukoli s odkazem na dalších $TIME$.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated.", + "placeholders": { + "time": { + "content": "$1", + "example": "7 days, 1 hour, 1 day" + } + } + }, + "sendCreatedDescriptionPassword": { + "message": "Zkopírujte a sdílejte tento odkaz Send. Send bude k dispozici komukoli s odkazem a heslem na dalších $TIME$.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated.", + "placeholders": { + "time": { + "content": "$1", + "example": "7 days, 1 hour, 1 day" + } + } + }, + "sendCreatedDescriptionEmail": { + "message": "Zkopírujte a sdílejte tento Send pro odesílání. Můžou jej zobrazit osoby, které jste zadali, a to po dobu $TIME$.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated.", + "placeholders": { + "time": { + "content": "$1", + "example": "7 days, 1 hour, 1 day" + } + } + }, "sendLinkCopied": { "message": "Odkaz Send byl zkopírován", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." @@ -6121,6 +6166,9 @@ "enterMultipleEmailsSeparatedByComma": { "message": "Zadejte více e-mailů oddělených čárkou." }, + "emailsRequiredChangeAccessType": { + "message": "Ověření e-mailu vyžaduje alespoň jednu e-mailovou adresu. Chcete-li odebrat všechny emaily, změňte výše uvedený typ přístupu." + }, "emailPlaceholder": { "message": "user@bitwarden.com , user@acme.com" }, @@ -6133,5 +6181,8 @@ "sendPasswordHelperText": { "message": "Pro zobrazení tohoto Send budou muset jednotlivci zadat heslo", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "userVerificationFailed": { + "message": "Ověření uživatele se nezdařilo." } } diff --git a/apps/browser/src/_locales/cy/messages.json b/apps/browser/src/_locales/cy/messages.json index 165cd05de8e..12dc8d7b44f 100644 --- a/apps/browser/src/_locales/cy/messages.json +++ b/apps/browser/src/_locales/cy/messages.json @@ -896,6 +896,9 @@ "invalidVerificationCode": { "message": "Cod dilysu annilys" }, + "invalidEmailOrVerificationCode": { + "message": "Invalid email or verification code" + }, "valueCopied": { "message": "$VALUE$ wedi'i gopïo", "description": "Value has been copied to the clipboard.", @@ -2857,6 +2860,9 @@ "reviewAtRiskLoginSlideImgAltPeriod": { "message": "Illustration of a list of logins that are at-risk." }, + "welcomeDialogGraphicAlt": { + "message": "Illustration of the layout of the Bitwarden vault page." + }, "generatePasswordSlideDesc": { "message": "Quickly generate a strong, unique password with the Bitwarden autofill menu on the at-risk site.", "description": "Description of the generate password slide on the at-risk password page carousel" @@ -3080,6 +3086,45 @@ } } }, + "durationTimeHours": { + "message": "$HOURS$ hours", + "placeholders": { + "hours": { + "content": "$1", + "example": "5" + } + } + }, + "sendCreatedDescriptionV2": { + "message": "Copy and share this Send link. The Send will be available to anyone with the link for the next $TIME$.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated.", + "placeholders": { + "time": { + "content": "$1", + "example": "7 days, 1 hour, 1 day" + } + } + }, + "sendCreatedDescriptionPassword": { + "message": "Copy and share this Send link. The Send will be available to anyone with the link and password you set for the next $TIME$.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated.", + "placeholders": { + "time": { + "content": "$1", + "example": "7 days, 1 hour, 1 day" + } + } + }, + "sendCreatedDescriptionEmail": { + "message": "Copy and share this Send link. It can be viewed by the people you specified for the next $TIME$.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated.", + "placeholders": { + "time": { + "content": "$1", + "example": "7 days, 1 hour, 1 day" + } + } + }, "sendLinkCopied": { "message": "Send link copied", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." @@ -6121,6 +6166,9 @@ "enterMultipleEmailsSeparatedByComma": { "message": "Enter multiple emails by separating with a comma." }, + "emailsRequiredChangeAccessType": { + "message": "Email verification requires at least one email address. To remove all emails, change the access type above." + }, "emailPlaceholder": { "message": "user@bitwarden.com , user@acme.com" }, @@ -6133,5 +6181,8 @@ "sendPasswordHelperText": { "message": "Individuals will need to enter the password to view this Send", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "userVerificationFailed": { + "message": "User verification failed." } } diff --git a/apps/browser/src/_locales/da/messages.json b/apps/browser/src/_locales/da/messages.json index 615cc6a2a0b..d19d07ee9f5 100644 --- a/apps/browser/src/_locales/da/messages.json +++ b/apps/browser/src/_locales/da/messages.json @@ -896,6 +896,9 @@ "invalidVerificationCode": { "message": "Ugyldig bekræftelseskode" }, + "invalidEmailOrVerificationCode": { + "message": "Invalid email or verification code" + }, "valueCopied": { "message": "$VALUE$ kopieret", "description": "Value has been copied to the clipboard.", @@ -2857,6 +2860,9 @@ "reviewAtRiskLoginSlideImgAltPeriod": { "message": "Illustration of a list of logins that are at-risk." }, + "welcomeDialogGraphicAlt": { + "message": "Illustration of the layout of the Bitwarden vault page." + }, "generatePasswordSlideDesc": { "message": "Quickly generate a strong, unique password with the Bitwarden autofill menu on the at-risk site.", "description": "Description of the generate password slide on the at-risk password page carousel" @@ -3080,6 +3086,45 @@ } } }, + "durationTimeHours": { + "message": "$HOURS$ hours", + "placeholders": { + "hours": { + "content": "$1", + "example": "5" + } + } + }, + "sendCreatedDescriptionV2": { + "message": "Copy and share this Send link. The Send will be available to anyone with the link for the next $TIME$.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated.", + "placeholders": { + "time": { + "content": "$1", + "example": "7 days, 1 hour, 1 day" + } + } + }, + "sendCreatedDescriptionPassword": { + "message": "Copy and share this Send link. The Send will be available to anyone with the link and password you set for the next $TIME$.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated.", + "placeholders": { + "time": { + "content": "$1", + "example": "7 days, 1 hour, 1 day" + } + } + }, + "sendCreatedDescriptionEmail": { + "message": "Copy and share this Send link. It can be viewed by the people you specified for the next $TIME$.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated.", + "placeholders": { + "time": { + "content": "$1", + "example": "7 days, 1 hour, 1 day" + } + } + }, "sendLinkCopied": { "message": "Send-link kopieret", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." @@ -6121,6 +6166,9 @@ "enterMultipleEmailsSeparatedByComma": { "message": "Enter multiple emails by separating with a comma." }, + "emailsRequiredChangeAccessType": { + "message": "Email verification requires at least one email address. To remove all emails, change the access type above." + }, "emailPlaceholder": { "message": "user@bitwarden.com , user@acme.com" }, @@ -6133,5 +6181,8 @@ "sendPasswordHelperText": { "message": "Individuals will need to enter the password to view this Send", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "userVerificationFailed": { + "message": "User verification failed." } } diff --git a/apps/browser/src/_locales/de/messages.json b/apps/browser/src/_locales/de/messages.json index 8f2b023bc00..8c33da9ae79 100644 --- a/apps/browser/src/_locales/de/messages.json +++ b/apps/browser/src/_locales/de/messages.json @@ -896,6 +896,9 @@ "invalidVerificationCode": { "message": "Ungültiger Verifizierungscode" }, + "invalidEmailOrVerificationCode": { + "message": "E-Mail oder Verifizierungscode ungültig" + }, "valueCopied": { "message": "$VALUE$ kopiert", "description": "Value has been copied to the clipboard.", @@ -2857,6 +2860,9 @@ "reviewAtRiskLoginSlideImgAltPeriod": { "message": "Illustration einer Liste gefährdeter Zugangsdaten." }, + "welcomeDialogGraphicAlt": { + "message": "Illustration of the layout of the Bitwarden vault page." + }, "generatePasswordSlideDesc": { "message": "Generiere schnell ein starkes, einzigartiges Passwort mit dem Bitwarden Auto-Ausfüllen-Menü auf der gefährdeten Website.", "description": "Description of the generate password slide on the at-risk password page carousel" @@ -3080,6 +3086,45 @@ } } }, + "durationTimeHours": { + "message": "$HOURS$ Stunden", + "placeholders": { + "hours": { + "content": "$1", + "example": "5" + } + } + }, + "sendCreatedDescriptionV2": { + "message": "Kopiere und teile diesen Send-Link. Das Send wird für jeden mit dem Link für die nächsten $TIME$ verfügbar sein.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated.", + "placeholders": { + "time": { + "content": "$1", + "example": "7 days, 1 hour, 1 day" + } + } + }, + "sendCreatedDescriptionPassword": { + "message": "Kopiere und teile diesen Send-Link. Das Send wird für jeden mit dem Link und dem von dir festgelegten Passwort für die nächsten $TIME$ verfügbar sein.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated.", + "placeholders": { + "time": { + "content": "$1", + "example": "7 days, 1 hour, 1 day" + } + } + }, + "sendCreatedDescriptionEmail": { + "message": "Kopiere und teile diesen Send-Link. Er kann von den von dir angegebenen Personen für die nächsten $TIME$ angesehen werden.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated.", + "placeholders": { + "time": { + "content": "$1", + "example": "7 days, 1 hour, 1 day" + } + } + }, "sendLinkCopied": { "message": "Send-Link kopiert", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." @@ -6121,6 +6166,9 @@ "enterMultipleEmailsSeparatedByComma": { "message": "Gib mehrere E-Mail-Adressen ein, indem du sie mit einem Komma trennst." }, + "emailsRequiredChangeAccessType": { + "message": "E-Mail-Verifizierung erfordert mindestens eine E-Mail-Adresse. Ändere den Zugriffstyp oben, um alle E-Mails zu entfernen." + }, "emailPlaceholder": { "message": "benutzer@bitwarden.com, benutzer@acme.com" }, @@ -6133,5 +6181,8 @@ "sendPasswordHelperText": { "message": "Personen müssen das Passwort eingeben, um dieses Send anzusehen", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "userVerificationFailed": { + "message": "Benutzerverifizierung fehlgeschlagen." } } diff --git a/apps/browser/src/_locales/el/messages.json b/apps/browser/src/_locales/el/messages.json index 68f7267825d..0e5fc2eaeb1 100644 --- a/apps/browser/src/_locales/el/messages.json +++ b/apps/browser/src/_locales/el/messages.json @@ -896,6 +896,9 @@ "invalidVerificationCode": { "message": "Μη έγκυρος κωδικός επαλήθευσης" }, + "invalidEmailOrVerificationCode": { + "message": "Invalid email or verification code" + }, "valueCopied": { "message": "$VALUE$ αντιγράφηκε", "description": "Value has been copied to the clipboard.", @@ -2857,6 +2860,9 @@ "reviewAtRiskLoginSlideImgAltPeriod": { "message": "Illustration of a list of logins that are at-risk." }, + "welcomeDialogGraphicAlt": { + "message": "Illustration of the layout of the Bitwarden vault page." + }, "generatePasswordSlideDesc": { "message": "Quickly generate a strong, unique password with the Bitwarden autofill menu on the at-risk site.", "description": "Description of the generate password slide on the at-risk password page carousel" @@ -3080,6 +3086,45 @@ } } }, + "durationTimeHours": { + "message": "$HOURS$ hours", + "placeholders": { + "hours": { + "content": "$1", + "example": "5" + } + } + }, + "sendCreatedDescriptionV2": { + "message": "Copy and share this Send link. The Send will be available to anyone with the link for the next $TIME$.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated.", + "placeholders": { + "time": { + "content": "$1", + "example": "7 days, 1 hour, 1 day" + } + } + }, + "sendCreatedDescriptionPassword": { + "message": "Copy and share this Send link. The Send will be available to anyone with the link and password you set for the next $TIME$.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated.", + "placeholders": { + "time": { + "content": "$1", + "example": "7 days, 1 hour, 1 day" + } + } + }, + "sendCreatedDescriptionEmail": { + "message": "Copy and share this Send link. It can be viewed by the people you specified for the next $TIME$.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated.", + "placeholders": { + "time": { + "content": "$1", + "example": "7 days, 1 hour, 1 day" + } + } + }, "sendLinkCopied": { "message": "Ο σύνδεσμος Send αντιγράφηκε", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." @@ -6121,6 +6166,9 @@ "enterMultipleEmailsSeparatedByComma": { "message": "Enter multiple emails by separating with a comma." }, + "emailsRequiredChangeAccessType": { + "message": "Email verification requires at least one email address. To remove all emails, change the access type above." + }, "emailPlaceholder": { "message": "user@bitwarden.com , user@acme.com" }, @@ -6133,5 +6181,8 @@ "sendPasswordHelperText": { "message": "Individuals will need to enter the password to view this Send", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "userVerificationFailed": { + "message": "User verification failed." } } diff --git a/apps/browser/src/_locales/en/messages.json b/apps/browser/src/_locales/en/messages.json index a221dc4f338..51ca51960d7 100644 --- a/apps/browser/src/_locales/en/messages.json +++ b/apps/browser/src/_locales/en/messages.json @@ -896,6 +896,9 @@ "invalidVerificationCode": { "message": "Invalid verification code" }, + "invalidEmailOrVerificationCode": { + "message": "Invalid email or verification code" + }, "valueCopied": { "message": "$VALUE$ copied", "description": "Value has been copied to the clipboard.", @@ -2857,6 +2860,9 @@ "reviewAtRiskLoginSlideImgAltPeriod": { "message": "Illustration of a list of logins that are at-risk." }, + "welcomeDialogGraphicAlt": { + "message": "Illustration of the layout of the Bitwarden vault page." + }, "generatePasswordSlideDesc": { "message": "Quickly generate a strong, unique password with the Bitwarden autofill menu on the at-risk site.", "description": "Description of the generate password slide on the at-risk password page carousel" @@ -6160,10 +6166,12 @@ "enterMultipleEmailsSeparatedByComma": { "message": "Enter multiple emails by separating with a comma." }, + "emailsRequiredChangeAccessType": { + "message": "Email verification requires at least one email address. To remove all emails, change the access type above." + }, "emailPlaceholder": { "message": "user@bitwarden.com , user@acme.com" }, - "downloadBitwardenApps": { "message": "Download Bitwarden apps" }, @@ -6173,5 +6181,8 @@ "sendPasswordHelperText": { "message": "Individuals will need to enter the password to view this Send", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "userVerificationFailed": { + "message": "User verification failed." } } diff --git a/apps/browser/src/_locales/en_GB/messages.json b/apps/browser/src/_locales/en_GB/messages.json index d61774df145..be564f8a950 100644 --- a/apps/browser/src/_locales/en_GB/messages.json +++ b/apps/browser/src/_locales/en_GB/messages.json @@ -896,6 +896,9 @@ "invalidVerificationCode": { "message": "Invalid verification code" }, + "invalidEmailOrVerificationCode": { + "message": "Invalid email or verification code" + }, "valueCopied": { "message": "$VALUE$ copied", "description": "Value has been copied to the clipboard.", @@ -2857,6 +2860,9 @@ "reviewAtRiskLoginSlideImgAltPeriod": { "message": "Illustration of a list of logins that are at-risk." }, + "welcomeDialogGraphicAlt": { + "message": "Illustration of the layout of the Bitwarden vault page." + }, "generatePasswordSlideDesc": { "message": "Quickly generate a strong, unique password with the Bitwarden autofill menu on the at-risk site.", "description": "Description of the generate password slide on the at-risk password page carousel" @@ -3080,6 +3086,45 @@ } } }, + "durationTimeHours": { + "message": "$HOURS$ hours", + "placeholders": { + "hours": { + "content": "$1", + "example": "5" + } + } + }, + "sendCreatedDescriptionV2": { + "message": "Copy and share this Send link. The Send will be available to anyone with the link for the next $TIME$.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated.", + "placeholders": { + "time": { + "content": "$1", + "example": "7 days, 1 hour, 1 day" + } + } + }, + "sendCreatedDescriptionPassword": { + "message": "Copy and share this Send link. The Send will be available to anyone with the link and password you set for the next $TIME$.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated.", + "placeholders": { + "time": { + "content": "$1", + "example": "7 days, 1 hour, 1 day" + } + } + }, + "sendCreatedDescriptionEmail": { + "message": "Copy and share this Send link. It can be viewed by the people you specified for the next $TIME$.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated.", + "placeholders": { + "time": { + "content": "$1", + "example": "7 days, 1 hour, 1 day" + } + } + }, "sendLinkCopied": { "message": "Send link copied", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." @@ -6121,6 +6166,9 @@ "enterMultipleEmailsSeparatedByComma": { "message": "Enter multiple emails by separating with a comma." }, + "emailsRequiredChangeAccessType": { + "message": "Email verification requires at least one email address. To remove all emails, change the access type above." + }, "emailPlaceholder": { "message": "user@bitwarden.com , user@acme.com" }, @@ -6133,5 +6181,8 @@ "sendPasswordHelperText": { "message": "Individuals will need to enter the password to view this Send", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "userVerificationFailed": { + "message": "User verification failed." } } diff --git a/apps/browser/src/_locales/en_IN/messages.json b/apps/browser/src/_locales/en_IN/messages.json index 3622ffce241..18e02ec48ca 100644 --- a/apps/browser/src/_locales/en_IN/messages.json +++ b/apps/browser/src/_locales/en_IN/messages.json @@ -896,6 +896,9 @@ "invalidVerificationCode": { "message": "Invalid verification code" }, + "invalidEmailOrVerificationCode": { + "message": "Invalid email or verification code" + }, "valueCopied": { "message": "$VALUE$ copied", "description": "Value has been copied to the clipboard.", @@ -2857,6 +2860,9 @@ "reviewAtRiskLoginSlideImgAltPeriod": { "message": "Illustration of a list of logins that are at-risk." }, + "welcomeDialogGraphicAlt": { + "message": "Illustration of the layout of the Bitwarden vault page." + }, "generatePasswordSlideDesc": { "message": "Quickly generate a strong, unique password with the Bitwarden autofill menu on the at-risk site.", "description": "Description of the generate password slide on the at-risk password page carousel" @@ -3080,6 +3086,45 @@ } } }, + "durationTimeHours": { + "message": "$HOURS$ hours", + "placeholders": { + "hours": { + "content": "$1", + "example": "5" + } + } + }, + "sendCreatedDescriptionV2": { + "message": "Copy and share this Send link. The Send will be available to anyone with the link for the next $TIME$.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated.", + "placeholders": { + "time": { + "content": "$1", + "example": "7 days, 1 hour, 1 day" + } + } + }, + "sendCreatedDescriptionPassword": { + "message": "Copy and share this Send link. The Send will be available to anyone with the link and password you set for the next $TIME$.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated.", + "placeholders": { + "time": { + "content": "$1", + "example": "7 days, 1 hour, 1 day" + } + } + }, + "sendCreatedDescriptionEmail": { + "message": "Copy and share this Send link. It can be viewed by the people you specified for the next $TIME$.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated.", + "placeholders": { + "time": { + "content": "$1", + "example": "7 days, 1 hour, 1 day" + } + } + }, "sendLinkCopied": { "message": "Send link copied", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." @@ -6121,6 +6166,9 @@ "enterMultipleEmailsSeparatedByComma": { "message": "Enter multiple emails by separating with a comma." }, + "emailsRequiredChangeAccessType": { + "message": "Email verification requires at least one email address. To remove all emails, change the access type above." + }, "emailPlaceholder": { "message": "user@bitwarden.com , user@acme.com" }, @@ -6133,5 +6181,8 @@ "sendPasswordHelperText": { "message": "Individuals will need to enter the password to view this Send", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "userVerificationFailed": { + "message": "User verification failed." } } diff --git a/apps/browser/src/_locales/es/messages.json b/apps/browser/src/_locales/es/messages.json index 131263ea4d9..45d2139fd6b 100644 --- a/apps/browser/src/_locales/es/messages.json +++ b/apps/browser/src/_locales/es/messages.json @@ -896,6 +896,9 @@ "invalidVerificationCode": { "message": "Código de verificación no válido" }, + "invalidEmailOrVerificationCode": { + "message": "Invalid email or verification code" + }, "valueCopied": { "message": "Valor de $VALUE$ copiado", "description": "Value has been copied to the clipboard.", @@ -2857,6 +2860,9 @@ "reviewAtRiskLoginSlideImgAltPeriod": { "message": "Illustration of a list of logins that are at-risk." }, + "welcomeDialogGraphicAlt": { + "message": "Illustration of the layout of the Bitwarden vault page." + }, "generatePasswordSlideDesc": { "message": "Genera rápidamente una contraseña segura y única con el menú de autocompletado de Bitwarden en el sitio en riesgo.", "description": "Description of the generate password slide on the at-risk password page carousel" @@ -3080,6 +3086,45 @@ } } }, + "durationTimeHours": { + "message": "$HOURS$ hours", + "placeholders": { + "hours": { + "content": "$1", + "example": "5" + } + } + }, + "sendCreatedDescriptionV2": { + "message": "Copy and share this Send link. The Send will be available to anyone with the link for the next $TIME$.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated.", + "placeholders": { + "time": { + "content": "$1", + "example": "7 days, 1 hour, 1 day" + } + } + }, + "sendCreatedDescriptionPassword": { + "message": "Copy and share this Send link. The Send will be available to anyone with the link and password you set for the next $TIME$.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated.", + "placeholders": { + "time": { + "content": "$1", + "example": "7 days, 1 hour, 1 day" + } + } + }, + "sendCreatedDescriptionEmail": { + "message": "Copy and share this Send link. It can be viewed by the people you specified for the next $TIME$.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated.", + "placeholders": { + "time": { + "content": "$1", + "example": "7 days, 1 hour, 1 day" + } + } + }, "sendLinkCopied": { "message": "Enlace del Send copiado", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." @@ -6121,6 +6166,9 @@ "enterMultipleEmailsSeparatedByComma": { "message": "Introduce varios correos electrónicos separándolos con una coma." }, + "emailsRequiredChangeAccessType": { + "message": "Email verification requires at least one email address. To remove all emails, change the access type above." + }, "emailPlaceholder": { "message": "user@bitwarden.com , user@acme.com" }, @@ -6133,5 +6181,8 @@ "sendPasswordHelperText": { "message": "Los individuos tendrán que introducir la contraseña para ver este Send", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "userVerificationFailed": { + "message": "User verification failed." } } diff --git a/apps/browser/src/_locales/et/messages.json b/apps/browser/src/_locales/et/messages.json index cd78c444c89..789454218e1 100644 --- a/apps/browser/src/_locales/et/messages.json +++ b/apps/browser/src/_locales/et/messages.json @@ -896,6 +896,9 @@ "invalidVerificationCode": { "message": "Vale kinnituskood" }, + "invalidEmailOrVerificationCode": { + "message": "Invalid email or verification code" + }, "valueCopied": { "message": "$VALUE$ on kopeeritud", "description": "Value has been copied to the clipboard.", @@ -2857,6 +2860,9 @@ "reviewAtRiskLoginSlideImgAltPeriod": { "message": "Illustration of a list of logins that are at-risk." }, + "welcomeDialogGraphicAlt": { + "message": "Illustration of the layout of the Bitwarden vault page." + }, "generatePasswordSlideDesc": { "message": "Quickly generate a strong, unique password with the Bitwarden autofill menu on the at-risk site.", "description": "Description of the generate password slide on the at-risk password page carousel" @@ -3080,6 +3086,45 @@ } } }, + "durationTimeHours": { + "message": "$HOURS$ hours", + "placeholders": { + "hours": { + "content": "$1", + "example": "5" + } + } + }, + "sendCreatedDescriptionV2": { + "message": "Copy and share this Send link. The Send will be available to anyone with the link for the next $TIME$.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated.", + "placeholders": { + "time": { + "content": "$1", + "example": "7 days, 1 hour, 1 day" + } + } + }, + "sendCreatedDescriptionPassword": { + "message": "Copy and share this Send link. The Send will be available to anyone with the link and password you set for the next $TIME$.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated.", + "placeholders": { + "time": { + "content": "$1", + "example": "7 days, 1 hour, 1 day" + } + } + }, + "sendCreatedDescriptionEmail": { + "message": "Copy and share this Send link. It can be viewed by the people you specified for the next $TIME$.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated.", + "placeholders": { + "time": { + "content": "$1", + "example": "7 days, 1 hour, 1 day" + } + } + }, "sendLinkCopied": { "message": "Send link copied", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." @@ -6121,6 +6166,9 @@ "enterMultipleEmailsSeparatedByComma": { "message": "Enter multiple emails by separating with a comma." }, + "emailsRequiredChangeAccessType": { + "message": "Email verification requires at least one email address. To remove all emails, change the access type above." + }, "emailPlaceholder": { "message": "user@bitwarden.com , user@acme.com" }, @@ -6133,5 +6181,8 @@ "sendPasswordHelperText": { "message": "Individuals will need to enter the password to view this Send", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "userVerificationFailed": { + "message": "User verification failed." } } diff --git a/apps/browser/src/_locales/eu/messages.json b/apps/browser/src/_locales/eu/messages.json index 3e4382a3d3b..f08604910af 100644 --- a/apps/browser/src/_locales/eu/messages.json +++ b/apps/browser/src/_locales/eu/messages.json @@ -896,6 +896,9 @@ "invalidVerificationCode": { "message": "Egiaztatze-kodea ez da baliozkoa" }, + "invalidEmailOrVerificationCode": { + "message": "Invalid email or verification code" + }, "valueCopied": { "message": "$VALUE$ kopiatuta", "description": "Value has been copied to the clipboard.", @@ -2857,6 +2860,9 @@ "reviewAtRiskLoginSlideImgAltPeriod": { "message": "Illustration of a list of logins that are at-risk." }, + "welcomeDialogGraphicAlt": { + "message": "Illustration of the layout of the Bitwarden vault page." + }, "generatePasswordSlideDesc": { "message": "Quickly generate a strong, unique password with the Bitwarden autofill menu on the at-risk site.", "description": "Description of the generate password slide on the at-risk password page carousel" @@ -3080,6 +3086,45 @@ } } }, + "durationTimeHours": { + "message": "$HOURS$ hours", + "placeholders": { + "hours": { + "content": "$1", + "example": "5" + } + } + }, + "sendCreatedDescriptionV2": { + "message": "Copy and share this Send link. The Send will be available to anyone with the link for the next $TIME$.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated.", + "placeholders": { + "time": { + "content": "$1", + "example": "7 days, 1 hour, 1 day" + } + } + }, + "sendCreatedDescriptionPassword": { + "message": "Copy and share this Send link. The Send will be available to anyone with the link and password you set for the next $TIME$.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated.", + "placeholders": { + "time": { + "content": "$1", + "example": "7 days, 1 hour, 1 day" + } + } + }, + "sendCreatedDescriptionEmail": { + "message": "Copy and share this Send link. It can be viewed by the people you specified for the next $TIME$.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated.", + "placeholders": { + "time": { + "content": "$1", + "example": "7 days, 1 hour, 1 day" + } + } + }, "sendLinkCopied": { "message": "Send link copied", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." @@ -6121,6 +6166,9 @@ "enterMultipleEmailsSeparatedByComma": { "message": "Enter multiple emails by separating with a comma." }, + "emailsRequiredChangeAccessType": { + "message": "Email verification requires at least one email address. To remove all emails, change the access type above." + }, "emailPlaceholder": { "message": "user@bitwarden.com , user@acme.com" }, @@ -6133,5 +6181,8 @@ "sendPasswordHelperText": { "message": "Individuals will need to enter the password to view this Send", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "userVerificationFailed": { + "message": "User verification failed." } } diff --git a/apps/browser/src/_locales/fa/messages.json b/apps/browser/src/_locales/fa/messages.json index 5bb22dc6292..4c2474d614c 100644 --- a/apps/browser/src/_locales/fa/messages.json +++ b/apps/browser/src/_locales/fa/messages.json @@ -896,6 +896,9 @@ "invalidVerificationCode": { "message": "کد تأیید نامعتبر است" }, + "invalidEmailOrVerificationCode": { + "message": "Invalid email or verification code" + }, "valueCopied": { "message": "$VALUE$ کپی شد", "description": "Value has been copied to the clipboard.", @@ -2857,6 +2860,9 @@ "reviewAtRiskLoginSlideImgAltPeriod": { "message": "تصویری از فهرست ورودهایی که در معرض خطر هستند." }, + "welcomeDialogGraphicAlt": { + "message": "Illustration of the layout of the Bitwarden vault page." + }, "generatePasswordSlideDesc": { "message": "با استفاده از منوی پر کردن خودکار Bitwarden در سایت در معرض خطر، به‌سرعت یک کلمه عبور قوی و منحصر به فرد تولید کنید.", "description": "Description of the generate password slide on the at-risk password page carousel" @@ -3080,6 +3086,45 @@ } } }, + "durationTimeHours": { + "message": "$HOURS$ hours", + "placeholders": { + "hours": { + "content": "$1", + "example": "5" + } + } + }, + "sendCreatedDescriptionV2": { + "message": "Copy and share this Send link. The Send will be available to anyone with the link for the next $TIME$.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated.", + "placeholders": { + "time": { + "content": "$1", + "example": "7 days, 1 hour, 1 day" + } + } + }, + "sendCreatedDescriptionPassword": { + "message": "Copy and share this Send link. The Send will be available to anyone with the link and password you set for the next $TIME$.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated.", + "placeholders": { + "time": { + "content": "$1", + "example": "7 days, 1 hour, 1 day" + } + } + }, + "sendCreatedDescriptionEmail": { + "message": "Copy and share this Send link. It can be viewed by the people you specified for the next $TIME$.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated.", + "placeholders": { + "time": { + "content": "$1", + "example": "7 days, 1 hour, 1 day" + } + } + }, "sendLinkCopied": { "message": "لینک ارسال کپی شد", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." @@ -6121,6 +6166,9 @@ "enterMultipleEmailsSeparatedByComma": { "message": "Enter multiple emails by separating with a comma." }, + "emailsRequiredChangeAccessType": { + "message": "Email verification requires at least one email address. To remove all emails, change the access type above." + }, "emailPlaceholder": { "message": "user@bitwarden.com , user@acme.com" }, @@ -6133,5 +6181,8 @@ "sendPasswordHelperText": { "message": "Individuals will need to enter the password to view this Send", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "userVerificationFailed": { + "message": "User verification failed." } } diff --git a/apps/browser/src/_locales/fi/messages.json b/apps/browser/src/_locales/fi/messages.json index 2f5b1ec4932..8f5d3c05a63 100644 --- a/apps/browser/src/_locales/fi/messages.json +++ b/apps/browser/src/_locales/fi/messages.json @@ -29,7 +29,7 @@ "message": "Kirjaudu pääsyavaimella" }, "unlockWithPasskey": { - "message": "Unlock with passkey" + "message": "Poista lukitus todentamisavaimella" }, "useSingleSignOn": { "message": "Käytä kertakirjautumista" @@ -440,7 +440,7 @@ "message": "Synkronointi" }, "syncNow": { - "message": "Sync now" + "message": "Synkronoi nyt" }, "lastSync": { "message": "Viimeisin synkronointi:" @@ -607,10 +607,10 @@ "message": "Näytä kaikki" }, "showAll": { - "message": "Show all" + "message": "Näytä kaikki" }, "viewLess": { - "message": "View less" + "message": "Näytä vähemmän" }, "viewLogin": { "message": "View login" @@ -896,6 +896,9 @@ "invalidVerificationCode": { "message": "Virheellinen todennuskoodi" }, + "invalidEmailOrVerificationCode": { + "message": "Invalid email or verification code" + }, "valueCopied": { "message": "$VALUE$ kopioitu", "description": "Value has been copied to the clipboard.", @@ -1492,7 +1495,7 @@ "message": "This file is using an outdated encryption method." }, "attachmentUpdated": { - "message": "Attachment updated" + "message": "Liite päivitetty" }, "file": { "message": "Tiedosto" @@ -1661,7 +1664,7 @@ "message": "Passkey authentication failed" }, "useADifferentLogInMethod": { - "message": "Use a different log in method" + "message": "Käytä vaihtoehtoista kirjautumistapaa" }, "awaitingSecurityKeyInteraction": { "message": "Odotetaan suojausavaimen aktivointia..." @@ -1953,7 +1956,7 @@ "message": "Erääntymisvuosi" }, "monthly": { - "message": "month" + "message": "kuukausi" }, "expiration": { "message": "Voimassaolo päättyy" @@ -2052,7 +2055,7 @@ "message": "Sähköposti" }, "emails": { - "message": "Emails" + "message": "Sähköpostit" }, "phone": { "message": "Puhelinnumero" @@ -2857,6 +2860,9 @@ "reviewAtRiskLoginSlideImgAltPeriod": { "message": "Kuvitus vaarantuneiden kirjautumistietojen luettelosta." }, + "welcomeDialogGraphicAlt": { + "message": "Illustration of the layout of the Bitwarden vault page." + }, "generatePasswordSlideDesc": { "message": "Luo vahva ja ainutlaatuinen salasana nopeasti Bitwardenin automaattitäytön valikosta vaarantuneella sivustolla.", "description": "Description of the generate password slide on the at-risk password page carousel" @@ -3080,6 +3086,45 @@ } } }, + "durationTimeHours": { + "message": "$HOURS$ hours", + "placeholders": { + "hours": { + "content": "$1", + "example": "5" + } + } + }, + "sendCreatedDescriptionV2": { + "message": "Copy and share this Send link. The Send will be available to anyone with the link for the next $TIME$.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated.", + "placeholders": { + "time": { + "content": "$1", + "example": "7 days, 1 hour, 1 day" + } + } + }, + "sendCreatedDescriptionPassword": { + "message": "Copy and share this Send link. The Send will be available to anyone with the link and password you set for the next $TIME$.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated.", + "placeholders": { + "time": { + "content": "$1", + "example": "7 days, 1 hour, 1 day" + } + } + }, + "sendCreatedDescriptionEmail": { + "message": "Copy and share this Send link. It can be viewed by the people you specified for the next $TIME$.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated.", + "placeholders": { + "time": { + "content": "$1", + "example": "7 days, 1 hour, 1 day" + } + } + }, "sendLinkCopied": { "message": "Send-linkki kopioitiin", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." @@ -4824,7 +4869,7 @@ "message": "Hallintapaneelista" }, "admin": { - "message": "Admin" + "message": "Ylläpitäjä" }, "automaticUserConfirmation": { "message": "Automatic user confirmation" @@ -4854,16 +4899,16 @@ "message": "Turned on automatic confirmation" }, "availableNow": { - "message": "Available now" + "message": "Saatavilla nyt" }, "accountSecurity": { "message": "Tilin suojaus" }, "phishingBlocker": { - "message": "Phishing Blocker" + "message": "Tietojenkalasteluhyökkäysten estäminen" }, "enablePhishingDetection": { - "message": "Phishing detection" + "message": "Tietojenkalasteluhyökkäysten tunnistaminen" }, "enablePhishingDetectionDesc": { "message": "Display warning before accessing suspected phishing sites" @@ -4981,7 +5026,7 @@ } }, "downloadAttachmentLabel": { - "message": "Download Attachment" + "message": "Lataa liite" }, "downloadBitwarden": { "message": "Lataa Bitwarden" @@ -5716,10 +5761,10 @@ "message": "This login is at-risk and missing a website. Add a website and change the password for stronger security." }, "vulnerablePassword": { - "message": "Vulnerable password." + "message": "Heikko salasana" }, "changeNow": { - "message": "Change now" + "message": "Vaihda nyt" }, "missingWebsite": { "message": "Missing website" @@ -5773,13 +5818,13 @@ "message": "Tervetuloa holviisi!" }, "phishingPageTitleV2": { - "message": "Phishing attempt detected" + "message": "Havaittu tietojenkalasteluhyökkäyksen yritys" }, "phishingPageSummary": { - "message": "The site you are attempting to visit is a known malicious site and a security risk." + "message": "Sivusto jota olet avaamassa on tunnetusti haitallinen ja sen avaaminen on turvallisuusriski" }, "phishingPageCloseTabV2": { - "message": "Close this tab" + "message": "Sulje tämä välilehti" }, "phishingPageContinueV2": { "message": "Jatka tälle sivustolle (ei suositeltavaa)" @@ -5893,10 +5938,10 @@ "description": "'WebAssembly' is a technical term and should not be translated." }, "showMore": { - "message": "Show more" + "message": "Näytä enemmän" }, "showLess": { - "message": "Show less" + "message": "Näytä vähemmän" }, "next": { "message": "Seuraava" @@ -6121,6 +6166,9 @@ "enterMultipleEmailsSeparatedByComma": { "message": "Enter multiple emails by separating with a comma." }, + "emailsRequiredChangeAccessType": { + "message": "Email verification requires at least one email address. To remove all emails, change the access type above." + }, "emailPlaceholder": { "message": "user@bitwarden.com , user@acme.com" }, @@ -6133,5 +6181,8 @@ "sendPasswordHelperText": { "message": "Individuals will need to enter the password to view this Send", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "userVerificationFailed": { + "message": "User verification failed." } } diff --git a/apps/browser/src/_locales/fil/messages.json b/apps/browser/src/_locales/fil/messages.json index abb06f0f19f..069be19dc86 100644 --- a/apps/browser/src/_locales/fil/messages.json +++ b/apps/browser/src/_locales/fil/messages.json @@ -896,6 +896,9 @@ "invalidVerificationCode": { "message": "Maling verification code" }, + "invalidEmailOrVerificationCode": { + "message": "Invalid email or verification code" + }, "valueCopied": { "message": "Kinopya ang $VALUE$", "description": "Value has been copied to the clipboard.", @@ -2857,6 +2860,9 @@ "reviewAtRiskLoginSlideImgAltPeriod": { "message": "Illustration of a list of logins that are at-risk." }, + "welcomeDialogGraphicAlt": { + "message": "Illustration of the layout of the Bitwarden vault page." + }, "generatePasswordSlideDesc": { "message": "Quickly generate a strong, unique password with the Bitwarden autofill menu on the at-risk site.", "description": "Description of the generate password slide on the at-risk password page carousel" @@ -3080,6 +3086,45 @@ } } }, + "durationTimeHours": { + "message": "$HOURS$ hours", + "placeholders": { + "hours": { + "content": "$1", + "example": "5" + } + } + }, + "sendCreatedDescriptionV2": { + "message": "Copy and share this Send link. The Send will be available to anyone with the link for the next $TIME$.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated.", + "placeholders": { + "time": { + "content": "$1", + "example": "7 days, 1 hour, 1 day" + } + } + }, + "sendCreatedDescriptionPassword": { + "message": "Copy and share this Send link. The Send will be available to anyone with the link and password you set for the next $TIME$.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated.", + "placeholders": { + "time": { + "content": "$1", + "example": "7 days, 1 hour, 1 day" + } + } + }, + "sendCreatedDescriptionEmail": { + "message": "Copy and share this Send link. It can be viewed by the people you specified for the next $TIME$.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated.", + "placeholders": { + "time": { + "content": "$1", + "example": "7 days, 1 hour, 1 day" + } + } + }, "sendLinkCopied": { "message": "Send link copied", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." @@ -6121,6 +6166,9 @@ "enterMultipleEmailsSeparatedByComma": { "message": "Enter multiple emails by separating with a comma." }, + "emailsRequiredChangeAccessType": { + "message": "Email verification requires at least one email address. To remove all emails, change the access type above." + }, "emailPlaceholder": { "message": "user@bitwarden.com , user@acme.com" }, @@ -6133,5 +6181,8 @@ "sendPasswordHelperText": { "message": "Individuals will need to enter the password to view this Send", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "userVerificationFailed": { + "message": "User verification failed." } } diff --git a/apps/browser/src/_locales/fr/messages.json b/apps/browser/src/_locales/fr/messages.json index 596315c4d3f..afd5415e249 100644 --- a/apps/browser/src/_locales/fr/messages.json +++ b/apps/browser/src/_locales/fr/messages.json @@ -29,7 +29,7 @@ "message": "Se connecter avec une clé d'accès" }, "unlockWithPasskey": { - "message": "Unlock with passkey" + "message": "Déverrouiller avec une clé d'accès" }, "useSingleSignOn": { "message": "Utiliser l'authentification unique" @@ -574,28 +574,28 @@ "message": "Les éléments archivés apparaîtront ici et seront exclus des résultats de recherche généraux et des suggestions de remplissage automatique." }, "itemArchiveToast": { - "message": "Item archived" + "message": "Élément archivé" }, "itemUnarchivedToast": { - "message": "Item unarchived" + "message": "Élément désarchivé" }, "archiveItem": { "message": "Archiver l'élément" }, "archiveItemDialogContent": { - "message": "Once archived, this item will be excluded from search results and autofill suggestions." + "message": "Une fois archivé, cet élément sera exclu des résultats de recherche et des suggestions de remplissage automatique." }, "archived": { - "message": "Archived" + "message": "Archivé" }, "unarchiveAndSave": { - "message": "Unarchive and save" + "message": "Désarchiver et enregistrer" }, "upgradeToUseArchive": { "message": "Une adhésion premium est requise pour utiliser Archive." }, "itemRestored": { - "message": "Item has been restored" + "message": "L'élément a été restauré" }, "edit": { "message": "Modifier" @@ -896,6 +896,9 @@ "invalidVerificationCode": { "message": "Code de vérification invalide" }, + "invalidEmailOrVerificationCode": { + "message": "Courriel ou code de vérification invalide" + }, "valueCopied": { "message": "$VALUE$ copié", "description": "Value has been copied to the clipboard.", @@ -988,10 +991,10 @@ "message": "Non" }, "noAuth": { - "message": "Anyone with the link" + "message": "Toute personne disposant du lien" }, "anyOneWithPassword": { - "message": "Anyone with a password set by you" + "message": "N'importe qui avec un mot de passe défini par vous" }, "location": { "message": "Emplacement" @@ -1318,7 +1321,7 @@ "description": "Default URI match detection for autofill." }, "defaultUriMatchDetectionDesc": { - "message": "Choisit la manière dont la détection des correspondances URI est gérée par défaut pour les connexions lors d'actions telles que la saisie automatique." + "message": "Choisissez le mode de traitement par défaut de la détection de correspondance URI, pour les connexions lors de l'exécution d'actions telles que le remplissage automatique." }, "theme": { "message": "Thème" @@ -1341,7 +1344,7 @@ "message": "Exporter à partir de" }, "exportVerb": { - "message": "Export", + "message": "Exporter", "description": "The verb form of the word Export" }, "exportNoun": { @@ -1353,7 +1356,7 @@ "description": "The noun form of the word Import" }, "importVerb": { - "message": "Import", + "message": "Importer", "description": "The verb form of the word Import" }, "fileFormat": { @@ -1552,13 +1555,13 @@ "message": "Options de connexion propriétaires à deux facteurs telles que YubiKey et Duo." }, "premiumSubscriptionEnded": { - "message": "Your Premium subscription ended" + "message": "Votre abonnement Premium est terminé" }, "archivePremiumRestart": { - "message": "To regain access to your archive, restart your Premium subscription. If you edit details for an archived item before restarting, it'll be moved back into your vault." + "message": "Pour récupérer l'accès à vos archives, redémarrez votre abonnement Premium. Si vous modifiez les détails d'un élément archivé avant de le redémarrer, il sera déplacé dans votre coffre." }, "restartPremium": { - "message": "Restart Premium" + "message": "Redémarrer Premium" }, "ppremiumSignUpReports": { "message": "Hygiène du mot de passe, santé du compte et rapports sur les brèches de données pour assurer la sécurité de votre coffre." @@ -2052,7 +2055,7 @@ "message": "Courriel" }, "emails": { - "message": "Emails" + "message": "Courriels" }, "phone": { "message": "Téléphone" @@ -2483,7 +2486,7 @@ "message": "Élément définitivement supprimé" }, "archivedItemRestored": { - "message": "Archived item restored" + "message": "Élément archivé restauré" }, "restoreItem": { "message": "Restaurer l'élément" @@ -2857,6 +2860,9 @@ "reviewAtRiskLoginSlideImgAltPeriod": { "message": "Illustration d'une liste de connexions à risque." }, + "welcomeDialogGraphicAlt": { + "message": "Illustration of the layout of the Bitwarden vault page." + }, "generatePasswordSlideDesc": { "message": "Générez rapidement un mot de passe fort et unique grâce au menu de saisie automatique de Bitwarden sur le site à risque.", "description": "Description of the generate password slide on the at-risk password page carousel" @@ -3080,6 +3086,45 @@ } } }, + "durationTimeHours": { + "message": "$HOURS$ heures", + "placeholders": { + "hours": { + "content": "$1", + "example": "5" + } + } + }, + "sendCreatedDescriptionV2": { + "message": "Copiez et partagez ce lien Send. Le Send sera accessible à toute personne disposant du lien pour les prochains $TIME$.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated.", + "placeholders": { + "time": { + "content": "$1", + "example": "7 days, 1 hour, 1 day" + } + } + }, + "sendCreatedDescriptionPassword": { + "message": "Copiez et partagez ce lien Send. Le Send sera accessible à toute personne possédant le lien et le mot de passe que vous avez défini pendant $TIME$.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated.", + "placeholders": { + "time": { + "content": "$1", + "example": "7 days, 1 hour, 1 day" + } + } + }, + "sendCreatedDescriptionEmail": { + "message": "Copiez et partagez ce lien Send. Il peut être vu par les personnes définies pendant $TIME$.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated.", + "placeholders": { + "time": { + "content": "$1", + "example": "7 days, 1 hour, 1 day" + } + } + }, "sendLinkCopied": { "message": "Lien Send copié", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." @@ -3350,10 +3395,10 @@ "message": "Erreur" }, "prfUnlockFailed": { - "message": "Failed to unlock with passkey. Please try again or use another unlock method." + "message": "Impossible de déverrouiller avec la clé d'accès. Veuillez réessayer ou utiliser une autre méthode de déverrouillage." }, "noPrfCredentialsAvailable": { - "message": "No PRF-enabled passkeys are available for unlock. Please log in with a passkey first." + "message": "Aucune clé d'accès PRF disponible pour le déverrouillage. Veuillez vous connecter avec une clé d'accès en premier lieu." }, "decryptionError": { "message": "Erreur de déchiffrement" @@ -4142,7 +4187,7 @@ "message": "Ok" }, "toggleSideNavigation": { - "message": "Basculer la navigation latérale" + "message": "Activer la navigation latérale" }, "skipToContent": { "message": "Accéder directement au contenu" @@ -4731,7 +4776,7 @@ } }, "moreOptionsLabelNoPlaceholder": { - "message": "More options" + "message": "Plus de paramètres" }, "moreOptionsTitle": { "message": "Plus d'options - $ITEMNAME$", @@ -4827,46 +4872,46 @@ "message": "Admin" }, "automaticUserConfirmation": { - "message": "Automatic user confirmation" + "message": "Confirmation d'utilisateur automatique" }, "automaticUserConfirmationHint": { - "message": "Automatically confirm pending users while this device is unlocked" + "message": "Confirmer automatiquement les utilisateurs en attente pendant que cet appareil est déverrouillé" }, "autoConfirmOnboardingCallout": { - "message": "Save time with automatic user confirmation" + "message": "Gagnez du temps grâce à la confirmation d'utilisateur automatique" }, "autoConfirmWarning": { - "message": "This could impact your organization’s data security. " + "message": "Cela peut avoir un impact sur la sécurité des données de votre organisation. " }, "autoConfirmWarningLink": { - "message": "Learn about the risks" + "message": "En apprendre plus sur les risques" }, "autoConfirmSetup": { - "message": "Automatically confirm new users" + "message": "Confirmer les nouveaux utilisateurs automatiquement" }, "autoConfirmSetupDesc": { - "message": "New users will be automatically confirmed while this device is unlocked." + "message": "Les nouveaux utilisateurs seront confirmés automatiquement pendant que cet appareil est déverrouillé." }, "autoConfirmSetupHint": { - "message": "What are the potential security risks?" + "message": "Quels-sont les potentiels risques de sécurité ?" }, "autoConfirmEnabled": { - "message": "Turned on automatic confirmation" + "message": "Confirmation automatique activée" }, "availableNow": { - "message": "Available now" + "message": "Disponible maintenant" }, "accountSecurity": { "message": "Sécurité du compte" }, "phishingBlocker": { - "message": "Phishing Blocker" + "message": "Bloqueur d'hameçonnage" }, "enablePhishingDetection": { - "message": "Phishing detection" + "message": "Détection de l'hameçonnage" }, "enablePhishingDetectionDesc": { - "message": "Display warning before accessing suspected phishing sites" + "message": "Afficher un avertissement avant d'accéder à des sites soupçonnés d'hameçonnage" }, "notifications": { "message": "Notifications" @@ -4981,7 +5026,7 @@ } }, "downloadAttachmentLabel": { - "message": "Download Attachment" + "message": "Télécharger la pièce jointe" }, "downloadBitwarden": { "message": "Télécharger Bitwarden" @@ -5123,10 +5168,10 @@ } }, "showMatchDetectionNoPlaceholder": { - "message": "Show match detection" + "message": "Afficher la détection de correspondance" }, "hideMatchDetectionNoPlaceholder": { - "message": "Hide match detection" + "message": "Masquer la détection de correspondance" }, "autoFillOnPageLoad": { "message": "Saisir automatiquement lors du chargement de la page ?" @@ -5362,10 +5407,10 @@ "message": "Emplacement de l'élément" }, "fileSends": { - "message": "Envoi de fichiers" + "message": "Sends de fichier" }, "textSends": { - "message": "Envoi de textes" + "message": "Sends de texte" }, "accountActions": { "message": "Actions du compte" @@ -5665,7 +5710,7 @@ "message": "Très large" }, "narrow": { - "message": "Narrow" + "message": "Réduire" }, "sshKeyWrongPassword": { "message": "Le mot de passe saisi est incorrect." @@ -5716,10 +5761,10 @@ "message": "Cet identifiant est à risques et manque un site web. Ajoutez un site web et changez le mot de passe pour une meilleure sécurité." }, "vulnerablePassword": { - "message": "Vulnerable password." + "message": "Mot de passe vulnérable." }, "changeNow": { - "message": "Change now" + "message": "Changer maintenant" }, "missingWebsite": { "message": "Site Web manquant" @@ -5961,7 +6006,7 @@ "message": "Numéro de carte" }, "errorCannotDecrypt": { - "message": "Error: Cannot decrypt" + "message": "Erreur : décryptage impossible" }, "removeMasterPasswordForOrgUserKeyConnector": { "message": "Votre organisation n'utilise plus les mots de passe principaux pour se connecter à Bitwarden. Pour continuer, vérifiez l'organisation et le domaine." @@ -6092,46 +6137,52 @@ } }, "acceptTransfer": { - "message": "Accept transfer" + "message": "Accepter le transfert" }, "declineAndLeave": { - "message": "Decline and leave" + "message": "Refuser et quitter" }, "whyAmISeeingThis": { - "message": "Why am I seeing this?" + "message": "Pourquoi vois-je ceci ?" }, "items": { - "message": "Items" + "message": "Éléments" }, "searchResults": { - "message": "Search results" + "message": "Résultats de la recherche" }, "resizeSideNavigation": { - "message": "Resize side navigation" + "message": "Ajuster la taille de la navigation latérale" }, "whoCanView": { - "message": "Who can view" + "message": "Qui peut visionner" }, "specificPeople": { - "message": "Specific people" + "message": "Personnes spécifiques" }, "emailVerificationDesc": { - "message": "After sharing this Send link, individuals will need to verify their email with a code to view this Send." + "message": "Après avoir partagé ce lien Send, les individus devront vérifier leur courriel à l'aide d'un code afin de voir ce Send." }, "enterMultipleEmailsSeparatedByComma": { - "message": "Enter multiple emails by separating with a comma." + "message": "Entrez plusieurs courriels en les séparant d'une virgule." + }, + "emailsRequiredChangeAccessType": { + "message": "La vérification de courriel requiert au moins une adresse courriel. Pour retirer toutes les adresses, changez le type d'accès ci-dessus." }, "emailPlaceholder": { - "message": "user@bitwarden.com , user@acme.com" + "message": "utilisateur@bitwarden.com , utilisateur@acme.com" }, "downloadBitwardenApps": { - "message": "Download Bitwarden apps" + "message": "Télécharger les applications Bitwarden" }, "emailProtected": { - "message": "Email protected" + "message": "Courriel protégé" }, "sendPasswordHelperText": { - "message": "Individuals will need to enter the password to view this Send", + "message": "Les individus devront entrer le mot de passe pour visionner ce Send", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "userVerificationFailed": { + "message": "Échec de la vérification d'utilisateur." } } diff --git a/apps/browser/src/_locales/gl/messages.json b/apps/browser/src/_locales/gl/messages.json index e710a489f9a..c84e1f56a95 100644 --- a/apps/browser/src/_locales/gl/messages.json +++ b/apps/browser/src/_locales/gl/messages.json @@ -896,6 +896,9 @@ "invalidVerificationCode": { "message": "Código de verificación non válido" }, + "invalidEmailOrVerificationCode": { + "message": "Invalid email or verification code" + }, "valueCopied": { "message": "$VALUE$ copiado", "description": "Value has been copied to the clipboard.", @@ -2857,6 +2860,9 @@ "reviewAtRiskLoginSlideImgAltPeriod": { "message": "Illustration of a list of logins that are at-risk." }, + "welcomeDialogGraphicAlt": { + "message": "Illustration of the layout of the Bitwarden vault page." + }, "generatePasswordSlideDesc": { "message": "Quickly generate a strong, unique password with the Bitwarden autofill menu on the at-risk site.", "description": "Description of the generate password slide on the at-risk password page carousel" @@ -3080,6 +3086,45 @@ } } }, + "durationTimeHours": { + "message": "$HOURS$ hours", + "placeholders": { + "hours": { + "content": "$1", + "example": "5" + } + } + }, + "sendCreatedDescriptionV2": { + "message": "Copy and share this Send link. The Send will be available to anyone with the link for the next $TIME$.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated.", + "placeholders": { + "time": { + "content": "$1", + "example": "7 days, 1 hour, 1 day" + } + } + }, + "sendCreatedDescriptionPassword": { + "message": "Copy and share this Send link. The Send will be available to anyone with the link and password you set for the next $TIME$.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated.", + "placeholders": { + "time": { + "content": "$1", + "example": "7 days, 1 hour, 1 day" + } + } + }, + "sendCreatedDescriptionEmail": { + "message": "Copy and share this Send link. It can be viewed by the people you specified for the next $TIME$.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated.", + "placeholders": { + "time": { + "content": "$1", + "example": "7 days, 1 hour, 1 day" + } + } + }, "sendLinkCopied": { "message": "Ligazón do Send copiado", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." @@ -6121,6 +6166,9 @@ "enterMultipleEmailsSeparatedByComma": { "message": "Enter multiple emails by separating with a comma." }, + "emailsRequiredChangeAccessType": { + "message": "Email verification requires at least one email address. To remove all emails, change the access type above." + }, "emailPlaceholder": { "message": "user@bitwarden.com , user@acme.com" }, @@ -6133,5 +6181,8 @@ "sendPasswordHelperText": { "message": "Individuals will need to enter the password to view this Send", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "userVerificationFailed": { + "message": "User verification failed." } } diff --git a/apps/browser/src/_locales/he/messages.json b/apps/browser/src/_locales/he/messages.json index a76cbb711a9..5462940fa63 100644 --- a/apps/browser/src/_locales/he/messages.json +++ b/apps/browser/src/_locales/he/messages.json @@ -896,6 +896,9 @@ "invalidVerificationCode": { "message": "קוד אימות שגוי" }, + "invalidEmailOrVerificationCode": { + "message": "Invalid email or verification code" + }, "valueCopied": { "message": "השדה $VALUE$ הועתק לזיכרון", "description": "Value has been copied to the clipboard.", @@ -2857,6 +2860,9 @@ "reviewAtRiskLoginSlideImgAltPeriod": { "message": "איור של רשימת כניסות בסיכון." }, + "welcomeDialogGraphicAlt": { + "message": "Illustration of the layout of the Bitwarden vault page." + }, "generatePasswordSlideDesc": { "message": "צור במהירות סיסמה חזקה וייחודית עם תפריט המילוי האוטומטי של Bitwarden באתר שבסיכון.", "description": "Description of the generate password slide on the at-risk password page carousel" @@ -3080,6 +3086,45 @@ } } }, + "durationTimeHours": { + "message": "$HOURS$ hours", + "placeholders": { + "hours": { + "content": "$1", + "example": "5" + } + } + }, + "sendCreatedDescriptionV2": { + "message": "Copy and share this Send link. The Send will be available to anyone with the link for the next $TIME$.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated.", + "placeholders": { + "time": { + "content": "$1", + "example": "7 days, 1 hour, 1 day" + } + } + }, + "sendCreatedDescriptionPassword": { + "message": "Copy and share this Send link. The Send will be available to anyone with the link and password you set for the next $TIME$.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated.", + "placeholders": { + "time": { + "content": "$1", + "example": "7 days, 1 hour, 1 day" + } + } + }, + "sendCreatedDescriptionEmail": { + "message": "Copy and share this Send link. It can be viewed by the people you specified for the next $TIME$.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated.", + "placeholders": { + "time": { + "content": "$1", + "example": "7 days, 1 hour, 1 day" + } + } + }, "sendLinkCopied": { "message": "קישור סֵנְד הועתק", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." @@ -6121,6 +6166,9 @@ "enterMultipleEmailsSeparatedByComma": { "message": "Enter multiple emails by separating with a comma." }, + "emailsRequiredChangeAccessType": { + "message": "Email verification requires at least one email address. To remove all emails, change the access type above." + }, "emailPlaceholder": { "message": "user@bitwarden.com , user@acme.com" }, @@ -6133,5 +6181,8 @@ "sendPasswordHelperText": { "message": "Individuals will need to enter the password to view this Send", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "userVerificationFailed": { + "message": "User verification failed." } } diff --git a/apps/browser/src/_locales/hi/messages.json b/apps/browser/src/_locales/hi/messages.json index d30cbd2cc6e..1fe7310a1f5 100644 --- a/apps/browser/src/_locales/hi/messages.json +++ b/apps/browser/src/_locales/hi/messages.json @@ -896,6 +896,9 @@ "invalidVerificationCode": { "message": "सत्यापन कोड अवैध है" }, + "invalidEmailOrVerificationCode": { + "message": "Invalid email or verification code" + }, "valueCopied": { "message": "$VALUE$ कॉपी हो गया है।", "description": "Value has been copied to the clipboard.", @@ -2857,6 +2860,9 @@ "reviewAtRiskLoginSlideImgAltPeriod": { "message": "जोखिमग्रस्त लॉगिन की सूची का चित्रण।" }, + "welcomeDialogGraphicAlt": { + "message": "Illustration of the layout of the Bitwarden vault page." + }, "generatePasswordSlideDesc": { "message": "Quickly generate a strong, unique password with the Bitwarden autofill menu on the at-risk site.", "description": "Description of the generate password slide on the at-risk password page carousel" @@ -3080,6 +3086,45 @@ } } }, + "durationTimeHours": { + "message": "$HOURS$ hours", + "placeholders": { + "hours": { + "content": "$1", + "example": "5" + } + } + }, + "sendCreatedDescriptionV2": { + "message": "Copy and share this Send link. The Send will be available to anyone with the link for the next $TIME$.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated.", + "placeholders": { + "time": { + "content": "$1", + "example": "7 days, 1 hour, 1 day" + } + } + }, + "sendCreatedDescriptionPassword": { + "message": "Copy and share this Send link. The Send will be available to anyone with the link and password you set for the next $TIME$.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated.", + "placeholders": { + "time": { + "content": "$1", + "example": "7 days, 1 hour, 1 day" + } + } + }, + "sendCreatedDescriptionEmail": { + "message": "Copy and share this Send link. It can be viewed by the people you specified for the next $TIME$.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated.", + "placeholders": { + "time": { + "content": "$1", + "example": "7 days, 1 hour, 1 day" + } + } + }, "sendLinkCopied": { "message": "Send link copied", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." @@ -6121,6 +6166,9 @@ "enterMultipleEmailsSeparatedByComma": { "message": "Enter multiple emails by separating with a comma." }, + "emailsRequiredChangeAccessType": { + "message": "Email verification requires at least one email address. To remove all emails, change the access type above." + }, "emailPlaceholder": { "message": "user@bitwarden.com , user@acme.com" }, @@ -6133,5 +6181,8 @@ "sendPasswordHelperText": { "message": "Individuals will need to enter the password to view this Send", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "userVerificationFailed": { + "message": "User verification failed." } } diff --git a/apps/browser/src/_locales/hr/messages.json b/apps/browser/src/_locales/hr/messages.json index 98fdee3b657..b73f52ffaf5 100644 --- a/apps/browser/src/_locales/hr/messages.json +++ b/apps/browser/src/_locales/hr/messages.json @@ -29,7 +29,7 @@ "message": "Prijava pristupnim ključem" }, "unlockWithPasskey": { - "message": "Unlock with passkey" + "message": "Otključaj prisutupnim ključem" }, "useSingleSignOn": { "message": "Jedinstvena prijava (SSO)" @@ -574,10 +574,10 @@ "message": "Arhivirane stavke biti će prikazane ovdje i biti će izuzete iz rezultata općih pretraga i preporuka auto-ispune." }, "itemArchiveToast": { - "message": "Item archived" + "message": "Stavka arhivirana" }, "itemUnarchivedToast": { - "message": "Item unarchived" + "message": "Stavka vraćena iz arhive" }, "archiveItem": { "message": "Arhiviraj stavku" @@ -896,6 +896,9 @@ "invalidVerificationCode": { "message": "Nevažeći kôd za provjeru" }, + "invalidEmailOrVerificationCode": { + "message": "Invalid email or verification code" + }, "valueCopied": { "message": " kopirano", "description": "Value has been copied to the clipboard.", @@ -2857,6 +2860,9 @@ "reviewAtRiskLoginSlideImgAltPeriod": { "message": "Ilustracija liste rizičnih prijava." }, + "welcomeDialogGraphicAlt": { + "message": "Illustration of the layout of the Bitwarden vault page." + }, "generatePasswordSlideDesc": { "message": "Brzo generiraj jake, jedinstvene lozinke koristeći Bitwarden dijalog auto-ispune direktno na stranici.", "description": "Description of the generate password slide on the at-risk password page carousel" @@ -3080,6 +3086,45 @@ } } }, + "durationTimeHours": { + "message": "$HOURS$ hours", + "placeholders": { + "hours": { + "content": "$1", + "example": "5" + } + } + }, + "sendCreatedDescriptionV2": { + "message": "Copy and share this Send link. The Send will be available to anyone with the link for the next $TIME$.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated.", + "placeholders": { + "time": { + "content": "$1", + "example": "7 days, 1 hour, 1 day" + } + } + }, + "sendCreatedDescriptionPassword": { + "message": "Copy and share this Send link. The Send will be available to anyone with the link and password you set for the next $TIME$.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated.", + "placeholders": { + "time": { + "content": "$1", + "example": "7 days, 1 hour, 1 day" + } + } + }, + "sendCreatedDescriptionEmail": { + "message": "Copy and share this Send link. It can be viewed by the people you specified for the next $TIME$.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated.", + "placeholders": { + "time": { + "content": "$1", + "example": "7 days, 1 hour, 1 day" + } + } + }, "sendLinkCopied": { "message": "Kopirana poveznica Senda", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." @@ -6121,6 +6166,9 @@ "enterMultipleEmailsSeparatedByComma": { "message": "Enter multiple emails by separating with a comma." }, + "emailsRequiredChangeAccessType": { + "message": "Email verification requires at least one email address. To remove all emails, change the access type above." + }, "emailPlaceholder": { "message": "user@bitwarden.com , user@acme.com" }, @@ -6133,5 +6181,8 @@ "sendPasswordHelperText": { "message": "Individuals will need to enter the password to view this Send", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "userVerificationFailed": { + "message": "User verification failed." } } diff --git a/apps/browser/src/_locales/hu/messages.json b/apps/browser/src/_locales/hu/messages.json index e6765219f15..5736378a638 100644 --- a/apps/browser/src/_locales/hu/messages.json +++ b/apps/browser/src/_locales/hu/messages.json @@ -896,6 +896,9 @@ "invalidVerificationCode": { "message": "Érvénytelen ellenőrző kód" }, + "invalidEmailOrVerificationCode": { + "message": "Az email cím vagy az ellenőrző kód érvénytelen." + }, "valueCopied": { "message": "$VALUE$ másolásra került.", "description": "Value has been copied to the clipboard.", @@ -2857,6 +2860,9 @@ "reviewAtRiskLoginSlideImgAltPeriod": { "message": "A kockázatos bejelentkezések listájának illusztrációja." }, + "welcomeDialogGraphicAlt": { + "message": "A Bitwarden széf oldal elrendezésének illusztrációja." + }, "generatePasswordSlideDesc": { "message": "Gyorsan generálhatunk erős, egyedi jelszót a Bitwarden automatikus kitöltési menüjével a kockázatos webhelyen.", "description": "Description of the generate password slide on the at-risk password page carousel" @@ -3080,6 +3086,45 @@ } } }, + "durationTimeHours": { + "message": "$HOURS$ óra", + "placeholders": { + "hours": { + "content": "$1", + "example": "5" + } + } + }, + "sendCreatedDescriptionV2": { + "message": "Másoljuk és osszuk meg ezt a Send elem hivatkozást. A Send elem bárki számára elérhető lesz, aki rendelkezik a hivatkozással a következő $TIME$ alatt.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated.", + "placeholders": { + "time": { + "content": "$1", + "example": "7 days, 1 hour, 1 day" + } + } + }, + "sendCreatedDescriptionPassword": { + "message": "Másoljuk és osszuk meg ezt a Send elem hivatkozást. A Send elem bárki számára elérhető lesz, aki rendelkezik a hivatkozással és a jelszóval a következő $TIME$ alatt.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated.", + "placeholders": { + "time": { + "content": "$1", + "example": "7 days, 1 hour, 1 day" + } + } + }, + "sendCreatedDescriptionEmail": { + "message": "Másoljuk és osszuk meg ezt a Send hivatkozást. Megtekinthetik a megadott személyek a következő $TIME$ intervallumban.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated.", + "placeholders": { + "time": { + "content": "$1", + "example": "7 days, 1 hour, 1 day" + } + } + }, "sendLinkCopied": { "message": "A Send hivatkozás másolásra került.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." @@ -6121,6 +6166,9 @@ "enterMultipleEmailsSeparatedByComma": { "message": "Írjunk be több email címet vesszővel elválasztva." }, + "emailsRequiredChangeAccessType": { + "message": "Az email cím ellenőrzéshez legalább egy email cím szükséges. Az összes email cím eltávolításához módosítsuk a fenti hozzáférési típust." + }, "emailPlaceholder": { "message": "user@bitwarden.com , user@acme.com" }, @@ -6133,5 +6181,8 @@ "sendPasswordHelperText": { "message": "A személyeknek meg kell adniuk a jelszót a Send elem megtekintéséhez.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "userVerificationFailed": { + "message": "A felhasználó ellenőrzése sikertelen volt." } } diff --git a/apps/browser/src/_locales/id/messages.json b/apps/browser/src/_locales/id/messages.json index ccf35569f36..63e975466ed 100644 --- a/apps/browser/src/_locales/id/messages.json +++ b/apps/browser/src/_locales/id/messages.json @@ -896,6 +896,9 @@ "invalidVerificationCode": { "message": "Kode verifikasi tidak valid" }, + "invalidEmailOrVerificationCode": { + "message": "Invalid email or verification code" + }, "valueCopied": { "message": "$VALUE$ disalin", "description": "Value has been copied to the clipboard.", @@ -2857,6 +2860,9 @@ "reviewAtRiskLoginSlideImgAltPeriod": { "message": "Gambaran daftar info masuk yang berpotensi bahaya." }, + "welcomeDialogGraphicAlt": { + "message": "Illustration of the layout of the Bitwarden vault page." + }, "generatePasswordSlideDesc": { "message": "Hasilkan kata sandi yang kuat dan unik dengan cepat dengan menu isi otomatis Bitwarden pada situs yang berpotensi bahaya.", "description": "Description of the generate password slide on the at-risk password page carousel" @@ -3080,6 +3086,45 @@ } } }, + "durationTimeHours": { + "message": "$HOURS$ hours", + "placeholders": { + "hours": { + "content": "$1", + "example": "5" + } + } + }, + "sendCreatedDescriptionV2": { + "message": "Copy and share this Send link. The Send will be available to anyone with the link for the next $TIME$.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated.", + "placeholders": { + "time": { + "content": "$1", + "example": "7 days, 1 hour, 1 day" + } + } + }, + "sendCreatedDescriptionPassword": { + "message": "Copy and share this Send link. The Send will be available to anyone with the link and password you set for the next $TIME$.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated.", + "placeholders": { + "time": { + "content": "$1", + "example": "7 days, 1 hour, 1 day" + } + } + }, + "sendCreatedDescriptionEmail": { + "message": "Copy and share this Send link. It can be viewed by the people you specified for the next $TIME$.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated.", + "placeholders": { + "time": { + "content": "$1", + "example": "7 days, 1 hour, 1 day" + } + } + }, "sendLinkCopied": { "message": "Tautan Send disalin", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." @@ -6121,6 +6166,9 @@ "enterMultipleEmailsSeparatedByComma": { "message": "Enter multiple emails by separating with a comma." }, + "emailsRequiredChangeAccessType": { + "message": "Email verification requires at least one email address. To remove all emails, change the access type above." + }, "emailPlaceholder": { "message": "user@bitwarden.com , user@acme.com" }, @@ -6133,5 +6181,8 @@ "sendPasswordHelperText": { "message": "Individuals will need to enter the password to view this Send", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "userVerificationFailed": { + "message": "User verification failed." } } diff --git a/apps/browser/src/_locales/it/messages.json b/apps/browser/src/_locales/it/messages.json index 42efa025207..f1d704c6d48 100644 --- a/apps/browser/src/_locales/it/messages.json +++ b/apps/browser/src/_locales/it/messages.json @@ -574,10 +574,10 @@ "message": "Gli elementi archiviati compariranno qui e saranno esclusi dai risultati di ricerca e suggerimenti di autoriempimento." }, "itemArchiveToast": { - "message": "Item archived" + "message": "Elemento archiviato" }, "itemUnarchivedToast": { - "message": "Item unarchived" + "message": "Elemento estratto dall'archivio" }, "archiveItem": { "message": "Archivia elemento" @@ -896,6 +896,9 @@ "invalidVerificationCode": { "message": "Codice di verifica non valido" }, + "invalidEmailOrVerificationCode": { + "message": "Codice di verifica non valido" + }, "valueCopied": { "message": "$VALUE$ copiata", "description": "Value has been copied to the clipboard.", @@ -2857,6 +2860,9 @@ "reviewAtRiskLoginSlideImgAltPeriod": { "message": "Illustrazione di una lista di login a rischio." }, + "welcomeDialogGraphicAlt": { + "message": "Illustrazione del layout di pagina della cassaforte Bitwarden." + }, "generatePasswordSlideDesc": { "message": "Genera rapidamente una parola d'accesso forte e unica con il menu' di riempimento automatico Bitwarden nel sito a rischio.", "description": "Description of the generate password slide on the at-risk password page carousel" @@ -3080,6 +3086,45 @@ } } }, + "durationTimeHours": { + "message": "$HOURS$ ore", + "placeholders": { + "hours": { + "content": "$1", + "example": "5" + } + } + }, + "sendCreatedDescriptionV2": { + "message": "Copia e condividi questo link di Send. Sarà disponibile a chiunque abbia il link per $TIME$.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated.", + "placeholders": { + "time": { + "content": "$1", + "example": "7 days, 1 hour, 1 day" + } + } + }, + "sendCreatedDescriptionPassword": { + "message": "Copia e condividi questo link di Send. Sarà disponibile a chiunque abbia link e password per $TIME$.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated.", + "placeholders": { + "time": { + "content": "$1", + "example": "7 days, 1 hour, 1 day" + } + } + }, + "sendCreatedDescriptionEmail": { + "message": "Copia e condividi questo link Send: potrà essere visualizzato dalle persone che hai specificato per $TIME$.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated.", + "placeholders": { + "time": { + "content": "$1", + "example": "7 days, 1 hour, 1 day" + } + } + }, "sendLinkCopied": { "message": "Link del Send copiato", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." @@ -5961,7 +6006,7 @@ "message": "Numero di carta" }, "errorCannotDecrypt": { - "message": "Error: Cannot decrypt" + "message": "Errore: impossibile decrittare" }, "removeMasterPasswordForOrgUserKeyConnector": { "message": "La tua organizzazione non utilizza più le password principali per accedere a Bitwarden. Per continuare, verifica l'organizzazione e il dominio." @@ -6101,10 +6146,10 @@ "message": "Perché vedo questo avviso?" }, "items": { - "message": "Items" + "message": "Elementi" }, "searchResults": { - "message": "Search results" + "message": "Risultati di ricerca" }, "resizeSideNavigation": { "message": "Ridimensiona la navigazione laterale" @@ -6121,17 +6166,23 @@ "enterMultipleEmailsSeparatedByComma": { "message": "Inserisci più indirizzi email separandoli con virgole." }, + "emailsRequiredChangeAccessType": { + "message": "La verifica via email richiede almeno un indirizzo email. Per rimuovere tutte le email, modifica il tipo di accesso qui sopra." + }, "emailPlaceholder": { "message": "user@bitwarden.com , user@acme.com" }, "downloadBitwardenApps": { - "message": "Download Bitwarden apps" + "message": "Scarica l'app Bitwarden" }, "emailProtected": { - "message": "Email protected" + "message": "Email protetta" }, "sendPasswordHelperText": { - "message": "Individuals will need to enter the password to view this Send", + "message": "I destinatari dovranno inserire la password per visualizzare questo Send", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "userVerificationFailed": { + "message": "Verifica dell'utente non riuscita." } } diff --git a/apps/browser/src/_locales/ja/messages.json b/apps/browser/src/_locales/ja/messages.json index 915308cec13..ecb03f3321d 100644 --- a/apps/browser/src/_locales/ja/messages.json +++ b/apps/browser/src/_locales/ja/messages.json @@ -896,6 +896,9 @@ "invalidVerificationCode": { "message": "認証コードが間違っています" }, + "invalidEmailOrVerificationCode": { + "message": "Invalid email or verification code" + }, "valueCopied": { "message": "$VALUE$ をコピーしました", "description": "Value has been copied to the clipboard.", @@ -2857,6 +2860,9 @@ "reviewAtRiskLoginSlideImgAltPeriod": { "message": "危険な状態にあるログイン情報の一覧表示の例" }, + "welcomeDialogGraphicAlt": { + "message": "Illustration of the layout of the Bitwarden vault page." + }, "generatePasswordSlideDesc": { "message": "Bitwarden の自動入力メニューで、強力で一意なパスワードをすぐに生成しましょう。", "description": "Description of the generate password slide on the at-risk password page carousel" @@ -3080,6 +3086,45 @@ } } }, + "durationTimeHours": { + "message": "$HOURS$ hours", + "placeholders": { + "hours": { + "content": "$1", + "example": "5" + } + } + }, + "sendCreatedDescriptionV2": { + "message": "Copy and share this Send link. The Send will be available to anyone with the link for the next $TIME$.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated.", + "placeholders": { + "time": { + "content": "$1", + "example": "7 days, 1 hour, 1 day" + } + } + }, + "sendCreatedDescriptionPassword": { + "message": "Copy and share this Send link. The Send will be available to anyone with the link and password you set for the next $TIME$.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated.", + "placeholders": { + "time": { + "content": "$1", + "example": "7 days, 1 hour, 1 day" + } + } + }, + "sendCreatedDescriptionEmail": { + "message": "Copy and share this Send link. It can be viewed by the people you specified for the next $TIME$.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated.", + "placeholders": { + "time": { + "content": "$1", + "example": "7 days, 1 hour, 1 day" + } + } + }, "sendLinkCopied": { "message": "Send リンクをコピーしました", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." @@ -6121,6 +6166,9 @@ "enterMultipleEmailsSeparatedByComma": { "message": "Enter multiple emails by separating with a comma." }, + "emailsRequiredChangeAccessType": { + "message": "Email verification requires at least one email address. To remove all emails, change the access type above." + }, "emailPlaceholder": { "message": "user@bitwarden.com , user@acme.com" }, @@ -6133,5 +6181,8 @@ "sendPasswordHelperText": { "message": "Individuals will need to enter the password to view this Send", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "userVerificationFailed": { + "message": "User verification failed." } } diff --git a/apps/browser/src/_locales/ka/messages.json b/apps/browser/src/_locales/ka/messages.json index 791664e6eec..17c86de13fb 100644 --- a/apps/browser/src/_locales/ka/messages.json +++ b/apps/browser/src/_locales/ka/messages.json @@ -896,6 +896,9 @@ "invalidVerificationCode": { "message": "არასწორი გადამოწმების კოდი" }, + "invalidEmailOrVerificationCode": { + "message": "Invalid email or verification code" + }, "valueCopied": { "message": "$VALUE$ copied", "description": "Value has been copied to the clipboard.", @@ -2857,6 +2860,9 @@ "reviewAtRiskLoginSlideImgAltPeriod": { "message": "Illustration of a list of logins that are at-risk." }, + "welcomeDialogGraphicAlt": { + "message": "Illustration of the layout of the Bitwarden vault page." + }, "generatePasswordSlideDesc": { "message": "Quickly generate a strong, unique password with the Bitwarden autofill menu on the at-risk site.", "description": "Description of the generate password slide on the at-risk password page carousel" @@ -3080,6 +3086,45 @@ } } }, + "durationTimeHours": { + "message": "$HOURS$ hours", + "placeholders": { + "hours": { + "content": "$1", + "example": "5" + } + } + }, + "sendCreatedDescriptionV2": { + "message": "Copy and share this Send link. The Send will be available to anyone with the link for the next $TIME$.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated.", + "placeholders": { + "time": { + "content": "$1", + "example": "7 days, 1 hour, 1 day" + } + } + }, + "sendCreatedDescriptionPassword": { + "message": "Copy and share this Send link. The Send will be available to anyone with the link and password you set for the next $TIME$.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated.", + "placeholders": { + "time": { + "content": "$1", + "example": "7 days, 1 hour, 1 day" + } + } + }, + "sendCreatedDescriptionEmail": { + "message": "Copy and share this Send link. It can be viewed by the people you specified for the next $TIME$.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated.", + "placeholders": { + "time": { + "content": "$1", + "example": "7 days, 1 hour, 1 day" + } + } + }, "sendLinkCopied": { "message": "Send link copied", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." @@ -6121,6 +6166,9 @@ "enterMultipleEmailsSeparatedByComma": { "message": "Enter multiple emails by separating with a comma." }, + "emailsRequiredChangeAccessType": { + "message": "Email verification requires at least one email address. To remove all emails, change the access type above." + }, "emailPlaceholder": { "message": "user@bitwarden.com , user@acme.com" }, @@ -6133,5 +6181,8 @@ "sendPasswordHelperText": { "message": "Individuals will need to enter the password to view this Send", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "userVerificationFailed": { + "message": "User verification failed." } } diff --git a/apps/browser/src/_locales/km/messages.json b/apps/browser/src/_locales/km/messages.json index c28007c3838..51ca51960d7 100644 --- a/apps/browser/src/_locales/km/messages.json +++ b/apps/browser/src/_locales/km/messages.json @@ -896,6 +896,9 @@ "invalidVerificationCode": { "message": "Invalid verification code" }, + "invalidEmailOrVerificationCode": { + "message": "Invalid email or verification code" + }, "valueCopied": { "message": "$VALUE$ copied", "description": "Value has been copied to the clipboard.", @@ -2857,6 +2860,9 @@ "reviewAtRiskLoginSlideImgAltPeriod": { "message": "Illustration of a list of logins that are at-risk." }, + "welcomeDialogGraphicAlt": { + "message": "Illustration of the layout of the Bitwarden vault page." + }, "generatePasswordSlideDesc": { "message": "Quickly generate a strong, unique password with the Bitwarden autofill menu on the at-risk site.", "description": "Description of the generate password slide on the at-risk password page carousel" @@ -3080,6 +3086,45 @@ } } }, + "durationTimeHours": { + "message": "$HOURS$ hours", + "placeholders": { + "hours": { + "content": "$1", + "example": "5" + } + } + }, + "sendCreatedDescriptionV2": { + "message": "Copy and share this Send link. The Send will be available to anyone with the link for the next $TIME$.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated.", + "placeholders": { + "time": { + "content": "$1", + "example": "7 days, 1 hour, 1 day" + } + } + }, + "sendCreatedDescriptionPassword": { + "message": "Copy and share this Send link. The Send will be available to anyone with the link and password you set for the next $TIME$.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated.", + "placeholders": { + "time": { + "content": "$1", + "example": "7 days, 1 hour, 1 day" + } + } + }, + "sendCreatedDescriptionEmail": { + "message": "Copy and share this Send link. It can be viewed by the people you specified for the next $TIME$.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated.", + "placeholders": { + "time": { + "content": "$1", + "example": "7 days, 1 hour, 1 day" + } + } + }, "sendLinkCopied": { "message": "Send link copied", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." @@ -6121,6 +6166,9 @@ "enterMultipleEmailsSeparatedByComma": { "message": "Enter multiple emails by separating with a comma." }, + "emailsRequiredChangeAccessType": { + "message": "Email verification requires at least one email address. To remove all emails, change the access type above." + }, "emailPlaceholder": { "message": "user@bitwarden.com , user@acme.com" }, @@ -6133,5 +6181,8 @@ "sendPasswordHelperText": { "message": "Individuals will need to enter the password to view this Send", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "userVerificationFailed": { + "message": "User verification failed." } } diff --git a/apps/browser/src/_locales/kn/messages.json b/apps/browser/src/_locales/kn/messages.json index faef7703a66..0dd4699a9a2 100644 --- a/apps/browser/src/_locales/kn/messages.json +++ b/apps/browser/src/_locales/kn/messages.json @@ -896,6 +896,9 @@ "invalidVerificationCode": { "message": "Invalid verification code" }, + "invalidEmailOrVerificationCode": { + "message": "Invalid email or verification code" + }, "valueCopied": { "message": "$VALUE$ ನಕಲಿಸಲಾಗಿದೆ", "description": "Value has been copied to the clipboard.", @@ -2857,6 +2860,9 @@ "reviewAtRiskLoginSlideImgAltPeriod": { "message": "Illustration of a list of logins that are at-risk." }, + "welcomeDialogGraphicAlt": { + "message": "Illustration of the layout of the Bitwarden vault page." + }, "generatePasswordSlideDesc": { "message": "Quickly generate a strong, unique password with the Bitwarden autofill menu on the at-risk site.", "description": "Description of the generate password slide on the at-risk password page carousel" @@ -3080,6 +3086,45 @@ } } }, + "durationTimeHours": { + "message": "$HOURS$ hours", + "placeholders": { + "hours": { + "content": "$1", + "example": "5" + } + } + }, + "sendCreatedDescriptionV2": { + "message": "Copy and share this Send link. The Send will be available to anyone with the link for the next $TIME$.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated.", + "placeholders": { + "time": { + "content": "$1", + "example": "7 days, 1 hour, 1 day" + } + } + }, + "sendCreatedDescriptionPassword": { + "message": "Copy and share this Send link. The Send will be available to anyone with the link and password you set for the next $TIME$.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated.", + "placeholders": { + "time": { + "content": "$1", + "example": "7 days, 1 hour, 1 day" + } + } + }, + "sendCreatedDescriptionEmail": { + "message": "Copy and share this Send link. It can be viewed by the people you specified for the next $TIME$.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated.", + "placeholders": { + "time": { + "content": "$1", + "example": "7 days, 1 hour, 1 day" + } + } + }, "sendLinkCopied": { "message": "Send link copied", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." @@ -6121,6 +6166,9 @@ "enterMultipleEmailsSeparatedByComma": { "message": "Enter multiple emails by separating with a comma." }, + "emailsRequiredChangeAccessType": { + "message": "Email verification requires at least one email address. To remove all emails, change the access type above." + }, "emailPlaceholder": { "message": "user@bitwarden.com , user@acme.com" }, @@ -6133,5 +6181,8 @@ "sendPasswordHelperText": { "message": "Individuals will need to enter the password to view this Send", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "userVerificationFailed": { + "message": "User verification failed." } } diff --git a/apps/browser/src/_locales/ko/messages.json b/apps/browser/src/_locales/ko/messages.json index b4a04e75e43..66467d99888 100644 --- a/apps/browser/src/_locales/ko/messages.json +++ b/apps/browser/src/_locales/ko/messages.json @@ -896,6 +896,9 @@ "invalidVerificationCode": { "message": "유효하지 않은 확인 코드" }, + "invalidEmailOrVerificationCode": { + "message": "Invalid email or verification code" + }, "valueCopied": { "message": "$VALUE$를 클립보드에 복사함", "description": "Value has been copied to the clipboard.", @@ -2857,6 +2860,9 @@ "reviewAtRiskLoginSlideImgAltPeriod": { "message": "Illustration of a list of logins that are at-risk." }, + "welcomeDialogGraphicAlt": { + "message": "Illustration of the layout of the Bitwarden vault page." + }, "generatePasswordSlideDesc": { "message": "Quickly generate a strong, unique password with the Bitwarden autofill menu on the at-risk site.", "description": "Description of the generate password slide on the at-risk password page carousel" @@ -3080,6 +3086,45 @@ } } }, + "durationTimeHours": { + "message": "$HOURS$ hours", + "placeholders": { + "hours": { + "content": "$1", + "example": "5" + } + } + }, + "sendCreatedDescriptionV2": { + "message": "Copy and share this Send link. The Send will be available to anyone with the link for the next $TIME$.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated.", + "placeholders": { + "time": { + "content": "$1", + "example": "7 days, 1 hour, 1 day" + } + } + }, + "sendCreatedDescriptionPassword": { + "message": "Copy and share this Send link. The Send will be available to anyone with the link and password you set for the next $TIME$.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated.", + "placeholders": { + "time": { + "content": "$1", + "example": "7 days, 1 hour, 1 day" + } + } + }, + "sendCreatedDescriptionEmail": { + "message": "Copy and share this Send link. It can be viewed by the people you specified for the next $TIME$.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated.", + "placeholders": { + "time": { + "content": "$1", + "example": "7 days, 1 hour, 1 day" + } + } + }, "sendLinkCopied": { "message": "Send 링크 복사됨", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." @@ -6121,6 +6166,9 @@ "enterMultipleEmailsSeparatedByComma": { "message": "Enter multiple emails by separating with a comma." }, + "emailsRequiredChangeAccessType": { + "message": "Email verification requires at least one email address. To remove all emails, change the access type above." + }, "emailPlaceholder": { "message": "user@bitwarden.com , user@acme.com" }, @@ -6133,5 +6181,8 @@ "sendPasswordHelperText": { "message": "Individuals will need to enter the password to view this Send", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "userVerificationFailed": { + "message": "User verification failed." } } diff --git a/apps/browser/src/_locales/lt/messages.json b/apps/browser/src/_locales/lt/messages.json index 68eb11aa234..a4cfb34942c 100644 --- a/apps/browser/src/_locales/lt/messages.json +++ b/apps/browser/src/_locales/lt/messages.json @@ -896,6 +896,9 @@ "invalidVerificationCode": { "message": "Neteisingas patvirtinimo kodas" }, + "invalidEmailOrVerificationCode": { + "message": "Invalid email or verification code" + }, "valueCopied": { "message": "Nukopijuota $VALUE$", "description": "Value has been copied to the clipboard.", @@ -2857,6 +2860,9 @@ "reviewAtRiskLoginSlideImgAltPeriod": { "message": "Illustration of a list of logins that are at-risk." }, + "welcomeDialogGraphicAlt": { + "message": "Illustration of the layout of the Bitwarden vault page." + }, "generatePasswordSlideDesc": { "message": "Quickly generate a strong, unique password with the Bitwarden autofill menu on the at-risk site.", "description": "Description of the generate password slide on the at-risk password page carousel" @@ -3080,6 +3086,45 @@ } } }, + "durationTimeHours": { + "message": "$HOURS$ hours", + "placeholders": { + "hours": { + "content": "$1", + "example": "5" + } + } + }, + "sendCreatedDescriptionV2": { + "message": "Copy and share this Send link. The Send will be available to anyone with the link for the next $TIME$.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated.", + "placeholders": { + "time": { + "content": "$1", + "example": "7 days, 1 hour, 1 day" + } + } + }, + "sendCreatedDescriptionPassword": { + "message": "Copy and share this Send link. The Send will be available to anyone with the link and password you set for the next $TIME$.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated.", + "placeholders": { + "time": { + "content": "$1", + "example": "7 days, 1 hour, 1 day" + } + } + }, + "sendCreatedDescriptionEmail": { + "message": "Copy and share this Send link. It can be viewed by the people you specified for the next $TIME$.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated.", + "placeholders": { + "time": { + "content": "$1", + "example": "7 days, 1 hour, 1 day" + } + } + }, "sendLinkCopied": { "message": "Send link copied", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." @@ -6121,6 +6166,9 @@ "enterMultipleEmailsSeparatedByComma": { "message": "Enter multiple emails by separating with a comma." }, + "emailsRequiredChangeAccessType": { + "message": "Email verification requires at least one email address. To remove all emails, change the access type above." + }, "emailPlaceholder": { "message": "user@bitwarden.com , user@acme.com" }, @@ -6133,5 +6181,8 @@ "sendPasswordHelperText": { "message": "Individuals will need to enter the password to view this Send", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "userVerificationFailed": { + "message": "User verification failed." } } diff --git a/apps/browser/src/_locales/lv/messages.json b/apps/browser/src/_locales/lv/messages.json index 6eaf545e390..d6e9a171978 100644 --- a/apps/browser/src/_locales/lv/messages.json +++ b/apps/browser/src/_locales/lv/messages.json @@ -574,10 +574,10 @@ "message": "Šeit parādīsies arhivētie vienumi, un tie netiks iekļauti vispārējās meklēšanas iznākumos un automātiskās aizpildes ieteikumos." }, "itemArchiveToast": { - "message": "Item archived" + "message": "Vienums ievietots arhīvā" }, "itemUnarchivedToast": { - "message": "Item unarchived" + "message": "Vienums izņemts no arhīva" }, "archiveItem": { "message": "Arhivēt vienumu" @@ -896,6 +896,9 @@ "invalidVerificationCode": { "message": "Nederīgs apliecinājuma kods" }, + "invalidEmailOrVerificationCode": { + "message": "Nederīga e-pasta adrese vai apliecinājuma kods" + }, "valueCopied": { "message": "$VALUE$ ir starpliktuvē", "description": "Value has been copied to the clipboard.", @@ -2857,6 +2860,9 @@ "reviewAtRiskLoginSlideImgAltPeriod": { "message": "Riskam pakļauto pieteikšanās vienumu saraksta attēlojums." }, + "welcomeDialogGraphicAlt": { + "message": "Illustration of the layout of the Bitwarden vault page." + }, "generatePasswordSlideDesc": { "message": "Riskam pakļauto vienumu vietnē ar automātiskās aizpildes izvēlni var ātri izveidot stipru, neatkārtojamu paroli.", "description": "Description of the generate password slide on the at-risk password page carousel" @@ -3080,6 +3086,45 @@ } } }, + "durationTimeHours": { + "message": "$HOURS$ hours", + "placeholders": { + "hours": { + "content": "$1", + "example": "5" + } + } + }, + "sendCreatedDescriptionV2": { + "message": "Copy and share this Send link. The Send will be available to anyone with the link for the next $TIME$.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated.", + "placeholders": { + "time": { + "content": "$1", + "example": "7 days, 1 hour, 1 day" + } + } + }, + "sendCreatedDescriptionPassword": { + "message": "Copy and share this Send link. The Send will be available to anyone with the link and password you set for the next $TIME$.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated.", + "placeholders": { + "time": { + "content": "$1", + "example": "7 days, 1 hour, 1 day" + } + } + }, + "sendCreatedDescriptionEmail": { + "message": "Copy and share this Send link. It can be viewed by the people you specified for the next $TIME$.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated.", + "placeholders": { + "time": { + "content": "$1", + "example": "7 days, 1 hour, 1 day" + } + } + }, "sendLinkCopied": { "message": "Send saite ievietota starpliktuvē", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." @@ -6121,6 +6166,9 @@ "enterMultipleEmailsSeparatedByComma": { "message": "Enter multiple emails by separating with a comma." }, + "emailsRequiredChangeAccessType": { + "message": "E-pasta apliecināšanai ir nepieciešama vismaz viena e-pasta adrese. Lai noņemtu visas e-pasta adreses, augstāk jānomaina piekļūšanas veids." + }, "emailPlaceholder": { "message": "user@bitwarden.com , user@acme.com" }, @@ -6133,5 +6181,8 @@ "sendPasswordHelperText": { "message": "Cilvēkiem būs jāievada parole, lai apskatītu šo Send", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "userVerificationFailed": { + "message": "Lietotāja apliecināšana neizdevās." } } diff --git a/apps/browser/src/_locales/ml/messages.json b/apps/browser/src/_locales/ml/messages.json index db48220ffbb..61b9f4d45ab 100644 --- a/apps/browser/src/_locales/ml/messages.json +++ b/apps/browser/src/_locales/ml/messages.json @@ -896,6 +896,9 @@ "invalidVerificationCode": { "message": "Invalid verification code" }, + "invalidEmailOrVerificationCode": { + "message": "Invalid email or verification code" + }, "valueCopied": { "message": "$VALUE$ പകർത്തി", "description": "Value has been copied to the clipboard.", @@ -2857,6 +2860,9 @@ "reviewAtRiskLoginSlideImgAltPeriod": { "message": "Illustration of a list of logins that are at-risk." }, + "welcomeDialogGraphicAlt": { + "message": "Illustration of the layout of the Bitwarden vault page." + }, "generatePasswordSlideDesc": { "message": "Quickly generate a strong, unique password with the Bitwarden autofill menu on the at-risk site.", "description": "Description of the generate password slide on the at-risk password page carousel" @@ -3080,6 +3086,45 @@ } } }, + "durationTimeHours": { + "message": "$HOURS$ hours", + "placeholders": { + "hours": { + "content": "$1", + "example": "5" + } + } + }, + "sendCreatedDescriptionV2": { + "message": "Copy and share this Send link. The Send will be available to anyone with the link for the next $TIME$.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated.", + "placeholders": { + "time": { + "content": "$1", + "example": "7 days, 1 hour, 1 day" + } + } + }, + "sendCreatedDescriptionPassword": { + "message": "Copy and share this Send link. The Send will be available to anyone with the link and password you set for the next $TIME$.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated.", + "placeholders": { + "time": { + "content": "$1", + "example": "7 days, 1 hour, 1 day" + } + } + }, + "sendCreatedDescriptionEmail": { + "message": "Copy and share this Send link. It can be viewed by the people you specified for the next $TIME$.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated.", + "placeholders": { + "time": { + "content": "$1", + "example": "7 days, 1 hour, 1 day" + } + } + }, "sendLinkCopied": { "message": "Send link copied", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." @@ -6121,6 +6166,9 @@ "enterMultipleEmailsSeparatedByComma": { "message": "Enter multiple emails by separating with a comma." }, + "emailsRequiredChangeAccessType": { + "message": "Email verification requires at least one email address. To remove all emails, change the access type above." + }, "emailPlaceholder": { "message": "user@bitwarden.com , user@acme.com" }, @@ -6133,5 +6181,8 @@ "sendPasswordHelperText": { "message": "Individuals will need to enter the password to view this Send", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "userVerificationFailed": { + "message": "User verification failed." } } diff --git a/apps/browser/src/_locales/mr/messages.json b/apps/browser/src/_locales/mr/messages.json index abf2f7db968..2a793784aa2 100644 --- a/apps/browser/src/_locales/mr/messages.json +++ b/apps/browser/src/_locales/mr/messages.json @@ -896,6 +896,9 @@ "invalidVerificationCode": { "message": "Invalid verification code" }, + "invalidEmailOrVerificationCode": { + "message": "Invalid email or verification code" + }, "valueCopied": { "message": "$VALUE$ copied", "description": "Value has been copied to the clipboard.", @@ -2857,6 +2860,9 @@ "reviewAtRiskLoginSlideImgAltPeriod": { "message": "Illustration of a list of logins that are at-risk." }, + "welcomeDialogGraphicAlt": { + "message": "Illustration of the layout of the Bitwarden vault page." + }, "generatePasswordSlideDesc": { "message": "Quickly generate a strong, unique password with the Bitwarden autofill menu on the at-risk site.", "description": "Description of the generate password slide on the at-risk password page carousel" @@ -3080,6 +3086,45 @@ } } }, + "durationTimeHours": { + "message": "$HOURS$ hours", + "placeholders": { + "hours": { + "content": "$1", + "example": "5" + } + } + }, + "sendCreatedDescriptionV2": { + "message": "Copy and share this Send link. The Send will be available to anyone with the link for the next $TIME$.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated.", + "placeholders": { + "time": { + "content": "$1", + "example": "7 days, 1 hour, 1 day" + } + } + }, + "sendCreatedDescriptionPassword": { + "message": "Copy and share this Send link. The Send will be available to anyone with the link and password you set for the next $TIME$.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated.", + "placeholders": { + "time": { + "content": "$1", + "example": "7 days, 1 hour, 1 day" + } + } + }, + "sendCreatedDescriptionEmail": { + "message": "Copy and share this Send link. It can be viewed by the people you specified for the next $TIME$.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated.", + "placeholders": { + "time": { + "content": "$1", + "example": "7 days, 1 hour, 1 day" + } + } + }, "sendLinkCopied": { "message": "Send link copied", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." @@ -6121,6 +6166,9 @@ "enterMultipleEmailsSeparatedByComma": { "message": "Enter multiple emails by separating with a comma." }, + "emailsRequiredChangeAccessType": { + "message": "Email verification requires at least one email address. To remove all emails, change the access type above." + }, "emailPlaceholder": { "message": "user@bitwarden.com , user@acme.com" }, @@ -6133,5 +6181,8 @@ "sendPasswordHelperText": { "message": "Individuals will need to enter the password to view this Send", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "userVerificationFailed": { + "message": "User verification failed." } } diff --git a/apps/browser/src/_locales/my/messages.json b/apps/browser/src/_locales/my/messages.json index c28007c3838..51ca51960d7 100644 --- a/apps/browser/src/_locales/my/messages.json +++ b/apps/browser/src/_locales/my/messages.json @@ -896,6 +896,9 @@ "invalidVerificationCode": { "message": "Invalid verification code" }, + "invalidEmailOrVerificationCode": { + "message": "Invalid email or verification code" + }, "valueCopied": { "message": "$VALUE$ copied", "description": "Value has been copied to the clipboard.", @@ -2857,6 +2860,9 @@ "reviewAtRiskLoginSlideImgAltPeriod": { "message": "Illustration of a list of logins that are at-risk." }, + "welcomeDialogGraphicAlt": { + "message": "Illustration of the layout of the Bitwarden vault page." + }, "generatePasswordSlideDesc": { "message": "Quickly generate a strong, unique password with the Bitwarden autofill menu on the at-risk site.", "description": "Description of the generate password slide on the at-risk password page carousel" @@ -3080,6 +3086,45 @@ } } }, + "durationTimeHours": { + "message": "$HOURS$ hours", + "placeholders": { + "hours": { + "content": "$1", + "example": "5" + } + } + }, + "sendCreatedDescriptionV2": { + "message": "Copy and share this Send link. The Send will be available to anyone with the link for the next $TIME$.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated.", + "placeholders": { + "time": { + "content": "$1", + "example": "7 days, 1 hour, 1 day" + } + } + }, + "sendCreatedDescriptionPassword": { + "message": "Copy and share this Send link. The Send will be available to anyone with the link and password you set for the next $TIME$.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated.", + "placeholders": { + "time": { + "content": "$1", + "example": "7 days, 1 hour, 1 day" + } + } + }, + "sendCreatedDescriptionEmail": { + "message": "Copy and share this Send link. It can be viewed by the people you specified for the next $TIME$.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated.", + "placeholders": { + "time": { + "content": "$1", + "example": "7 days, 1 hour, 1 day" + } + } + }, "sendLinkCopied": { "message": "Send link copied", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." @@ -6121,6 +6166,9 @@ "enterMultipleEmailsSeparatedByComma": { "message": "Enter multiple emails by separating with a comma." }, + "emailsRequiredChangeAccessType": { + "message": "Email verification requires at least one email address. To remove all emails, change the access type above." + }, "emailPlaceholder": { "message": "user@bitwarden.com , user@acme.com" }, @@ -6133,5 +6181,8 @@ "sendPasswordHelperText": { "message": "Individuals will need to enter the password to view this Send", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "userVerificationFailed": { + "message": "User verification failed." } } diff --git a/apps/browser/src/_locales/nb/messages.json b/apps/browser/src/_locales/nb/messages.json index 4689cb23b7a..d19dc3571e2 100644 --- a/apps/browser/src/_locales/nb/messages.json +++ b/apps/browser/src/_locales/nb/messages.json @@ -896,6 +896,9 @@ "invalidVerificationCode": { "message": "Ugyldig bekreftelseskode" }, + "invalidEmailOrVerificationCode": { + "message": "Invalid email or verification code" + }, "valueCopied": { "message": "$VALUE$ er kopiert", "description": "Value has been copied to the clipboard.", @@ -2857,6 +2860,9 @@ "reviewAtRiskLoginSlideImgAltPeriod": { "message": "Illustration of a list of logins that are at-risk." }, + "welcomeDialogGraphicAlt": { + "message": "Illustration of the layout of the Bitwarden vault page." + }, "generatePasswordSlideDesc": { "message": "Quickly generate a strong, unique password with the Bitwarden autofill menu on the at-risk site.", "description": "Description of the generate password slide on the at-risk password page carousel" @@ -3080,6 +3086,45 @@ } } }, + "durationTimeHours": { + "message": "$HOURS$ hours", + "placeholders": { + "hours": { + "content": "$1", + "example": "5" + } + } + }, + "sendCreatedDescriptionV2": { + "message": "Copy and share this Send link. The Send will be available to anyone with the link for the next $TIME$.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated.", + "placeholders": { + "time": { + "content": "$1", + "example": "7 days, 1 hour, 1 day" + } + } + }, + "sendCreatedDescriptionPassword": { + "message": "Copy and share this Send link. The Send will be available to anyone with the link and password you set for the next $TIME$.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated.", + "placeholders": { + "time": { + "content": "$1", + "example": "7 days, 1 hour, 1 day" + } + } + }, + "sendCreatedDescriptionEmail": { + "message": "Copy and share this Send link. It can be viewed by the people you specified for the next $TIME$.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated.", + "placeholders": { + "time": { + "content": "$1", + "example": "7 days, 1 hour, 1 day" + } + } + }, "sendLinkCopied": { "message": "Send-lenken ble kopiert", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." @@ -6121,6 +6166,9 @@ "enterMultipleEmailsSeparatedByComma": { "message": "Enter multiple emails by separating with a comma." }, + "emailsRequiredChangeAccessType": { + "message": "Email verification requires at least one email address. To remove all emails, change the access type above." + }, "emailPlaceholder": { "message": "user@bitwarden.com , user@acme.com" }, @@ -6133,5 +6181,8 @@ "sendPasswordHelperText": { "message": "Individuals will need to enter the password to view this Send", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "userVerificationFailed": { + "message": "User verification failed." } } diff --git a/apps/browser/src/_locales/ne/messages.json b/apps/browser/src/_locales/ne/messages.json index c28007c3838..51ca51960d7 100644 --- a/apps/browser/src/_locales/ne/messages.json +++ b/apps/browser/src/_locales/ne/messages.json @@ -896,6 +896,9 @@ "invalidVerificationCode": { "message": "Invalid verification code" }, + "invalidEmailOrVerificationCode": { + "message": "Invalid email or verification code" + }, "valueCopied": { "message": "$VALUE$ copied", "description": "Value has been copied to the clipboard.", @@ -2857,6 +2860,9 @@ "reviewAtRiskLoginSlideImgAltPeriod": { "message": "Illustration of a list of logins that are at-risk." }, + "welcomeDialogGraphicAlt": { + "message": "Illustration of the layout of the Bitwarden vault page." + }, "generatePasswordSlideDesc": { "message": "Quickly generate a strong, unique password with the Bitwarden autofill menu on the at-risk site.", "description": "Description of the generate password slide on the at-risk password page carousel" @@ -3080,6 +3086,45 @@ } } }, + "durationTimeHours": { + "message": "$HOURS$ hours", + "placeholders": { + "hours": { + "content": "$1", + "example": "5" + } + } + }, + "sendCreatedDescriptionV2": { + "message": "Copy and share this Send link. The Send will be available to anyone with the link for the next $TIME$.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated.", + "placeholders": { + "time": { + "content": "$1", + "example": "7 days, 1 hour, 1 day" + } + } + }, + "sendCreatedDescriptionPassword": { + "message": "Copy and share this Send link. The Send will be available to anyone with the link and password you set for the next $TIME$.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated.", + "placeholders": { + "time": { + "content": "$1", + "example": "7 days, 1 hour, 1 day" + } + } + }, + "sendCreatedDescriptionEmail": { + "message": "Copy and share this Send link. It can be viewed by the people you specified for the next $TIME$.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated.", + "placeholders": { + "time": { + "content": "$1", + "example": "7 days, 1 hour, 1 day" + } + } + }, "sendLinkCopied": { "message": "Send link copied", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." @@ -6121,6 +6166,9 @@ "enterMultipleEmailsSeparatedByComma": { "message": "Enter multiple emails by separating with a comma." }, + "emailsRequiredChangeAccessType": { + "message": "Email verification requires at least one email address. To remove all emails, change the access type above." + }, "emailPlaceholder": { "message": "user@bitwarden.com , user@acme.com" }, @@ -6133,5 +6181,8 @@ "sendPasswordHelperText": { "message": "Individuals will need to enter the password to view this Send", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "userVerificationFailed": { + "message": "User verification failed." } } diff --git a/apps/browser/src/_locales/nl/messages.json b/apps/browser/src/_locales/nl/messages.json index 044b3cfaa64..0d3ed844a15 100644 --- a/apps/browser/src/_locales/nl/messages.json +++ b/apps/browser/src/_locales/nl/messages.json @@ -896,6 +896,9 @@ "invalidVerificationCode": { "message": "Ongeldige verificatiecode" }, + "invalidEmailOrVerificationCode": { + "message": "Ongeldig e-mailadres verificatiecode" + }, "valueCopied": { "message": "$VALUE$ gekopieerd", "description": "Value has been copied to the clipboard.", @@ -2857,6 +2860,9 @@ "reviewAtRiskLoginSlideImgAltPeriod": { "message": "Voorbeeld van een lijst van risicovolle inloggegevens." }, + "welcomeDialogGraphicAlt": { + "message": "Illustratie van de lay-out van de Bitwarden-kluispagina." + }, "generatePasswordSlideDesc": { "message": "Genereer snel een sterk, uniek wachtwoord met het automatisch invulmenu van Bitwarden op de risicovolle website.", "description": "Description of the generate password slide on the at-risk password page carousel" @@ -3080,6 +3086,45 @@ } } }, + "durationTimeHours": { + "message": "$HOURS$ uur", + "placeholders": { + "hours": { + "content": "$1", + "example": "5" + } + } + }, + "sendCreatedDescriptionV2": { + "message": "Kopieer en deel deze Send-link. De Send is beschikbaar voor iedereen met de link voor de volgende $TIME$.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated.", + "placeholders": { + "time": { + "content": "$1", + "example": "7 days, 1 hour, 1 day" + } + } + }, + "sendCreatedDescriptionPassword": { + "message": "Kopieer en deel deze Send-link. De Send is beschikbaar voor iedereen met de link en het ingestelde wachtwoord voor de volgende $TIME$.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated.", + "placeholders": { + "time": { + "content": "$1", + "example": "7 days, 1 hour, 1 day" + } + } + }, + "sendCreatedDescriptionEmail": { + "message": "Kopieer en deel deze Send-link. Het kan worden bekeken door de mensen die je hebt opgegeven voor de volgende $TIME$.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated.", + "placeholders": { + "time": { + "content": "$1", + "example": "7 days, 1 hour, 1 day" + } + } + }, "sendLinkCopied": { "message": "Send-link gekopieerd", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." @@ -6121,6 +6166,9 @@ "enterMultipleEmailsSeparatedByComma": { "message": "Voer meerdere e-mailadressen in door te scheiden met een komma." }, + "emailsRequiredChangeAccessType": { + "message": "E-mailverificatie vereist ten minste één e-mailadres. Om alle e-mailadressen te verwijderen, moet je het toegangstype hierboven wijzigen." + }, "emailPlaceholder": { "message": "user@bitwarden.com , user@acme.com" }, @@ -6133,5 +6181,8 @@ "sendPasswordHelperText": { "message": "Individuen moeten het wachtwoord invoeren om deze Send te bekijken", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "userVerificationFailed": { + "message": "Gebruikersverificatie is mislukt." } } diff --git a/apps/browser/src/_locales/nn/messages.json b/apps/browser/src/_locales/nn/messages.json index c28007c3838..51ca51960d7 100644 --- a/apps/browser/src/_locales/nn/messages.json +++ b/apps/browser/src/_locales/nn/messages.json @@ -896,6 +896,9 @@ "invalidVerificationCode": { "message": "Invalid verification code" }, + "invalidEmailOrVerificationCode": { + "message": "Invalid email or verification code" + }, "valueCopied": { "message": "$VALUE$ copied", "description": "Value has been copied to the clipboard.", @@ -2857,6 +2860,9 @@ "reviewAtRiskLoginSlideImgAltPeriod": { "message": "Illustration of a list of logins that are at-risk." }, + "welcomeDialogGraphicAlt": { + "message": "Illustration of the layout of the Bitwarden vault page." + }, "generatePasswordSlideDesc": { "message": "Quickly generate a strong, unique password with the Bitwarden autofill menu on the at-risk site.", "description": "Description of the generate password slide on the at-risk password page carousel" @@ -3080,6 +3086,45 @@ } } }, + "durationTimeHours": { + "message": "$HOURS$ hours", + "placeholders": { + "hours": { + "content": "$1", + "example": "5" + } + } + }, + "sendCreatedDescriptionV2": { + "message": "Copy and share this Send link. The Send will be available to anyone with the link for the next $TIME$.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated.", + "placeholders": { + "time": { + "content": "$1", + "example": "7 days, 1 hour, 1 day" + } + } + }, + "sendCreatedDescriptionPassword": { + "message": "Copy and share this Send link. The Send will be available to anyone with the link and password you set for the next $TIME$.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated.", + "placeholders": { + "time": { + "content": "$1", + "example": "7 days, 1 hour, 1 day" + } + } + }, + "sendCreatedDescriptionEmail": { + "message": "Copy and share this Send link. It can be viewed by the people you specified for the next $TIME$.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated.", + "placeholders": { + "time": { + "content": "$1", + "example": "7 days, 1 hour, 1 day" + } + } + }, "sendLinkCopied": { "message": "Send link copied", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." @@ -6121,6 +6166,9 @@ "enterMultipleEmailsSeparatedByComma": { "message": "Enter multiple emails by separating with a comma." }, + "emailsRequiredChangeAccessType": { + "message": "Email verification requires at least one email address. To remove all emails, change the access type above." + }, "emailPlaceholder": { "message": "user@bitwarden.com , user@acme.com" }, @@ -6133,5 +6181,8 @@ "sendPasswordHelperText": { "message": "Individuals will need to enter the password to view this Send", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "userVerificationFailed": { + "message": "User verification failed." } } diff --git a/apps/browser/src/_locales/or/messages.json b/apps/browser/src/_locales/or/messages.json index c28007c3838..51ca51960d7 100644 --- a/apps/browser/src/_locales/or/messages.json +++ b/apps/browser/src/_locales/or/messages.json @@ -896,6 +896,9 @@ "invalidVerificationCode": { "message": "Invalid verification code" }, + "invalidEmailOrVerificationCode": { + "message": "Invalid email or verification code" + }, "valueCopied": { "message": "$VALUE$ copied", "description": "Value has been copied to the clipboard.", @@ -2857,6 +2860,9 @@ "reviewAtRiskLoginSlideImgAltPeriod": { "message": "Illustration of a list of logins that are at-risk." }, + "welcomeDialogGraphicAlt": { + "message": "Illustration of the layout of the Bitwarden vault page." + }, "generatePasswordSlideDesc": { "message": "Quickly generate a strong, unique password with the Bitwarden autofill menu on the at-risk site.", "description": "Description of the generate password slide on the at-risk password page carousel" @@ -3080,6 +3086,45 @@ } } }, + "durationTimeHours": { + "message": "$HOURS$ hours", + "placeholders": { + "hours": { + "content": "$1", + "example": "5" + } + } + }, + "sendCreatedDescriptionV2": { + "message": "Copy and share this Send link. The Send will be available to anyone with the link for the next $TIME$.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated.", + "placeholders": { + "time": { + "content": "$1", + "example": "7 days, 1 hour, 1 day" + } + } + }, + "sendCreatedDescriptionPassword": { + "message": "Copy and share this Send link. The Send will be available to anyone with the link and password you set for the next $TIME$.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated.", + "placeholders": { + "time": { + "content": "$1", + "example": "7 days, 1 hour, 1 day" + } + } + }, + "sendCreatedDescriptionEmail": { + "message": "Copy and share this Send link. It can be viewed by the people you specified for the next $TIME$.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated.", + "placeholders": { + "time": { + "content": "$1", + "example": "7 days, 1 hour, 1 day" + } + } + }, "sendLinkCopied": { "message": "Send link copied", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." @@ -6121,6 +6166,9 @@ "enterMultipleEmailsSeparatedByComma": { "message": "Enter multiple emails by separating with a comma." }, + "emailsRequiredChangeAccessType": { + "message": "Email verification requires at least one email address. To remove all emails, change the access type above." + }, "emailPlaceholder": { "message": "user@bitwarden.com , user@acme.com" }, @@ -6133,5 +6181,8 @@ "sendPasswordHelperText": { "message": "Individuals will need to enter the password to view this Send", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "userVerificationFailed": { + "message": "User verification failed." } } diff --git a/apps/browser/src/_locales/pl/messages.json b/apps/browser/src/_locales/pl/messages.json index 44c7b5fb6dd..c470af8c1dc 100644 --- a/apps/browser/src/_locales/pl/messages.json +++ b/apps/browser/src/_locales/pl/messages.json @@ -896,6 +896,9 @@ "invalidVerificationCode": { "message": "Kod weryfikacyjny jest nieprawidłowy" }, + "invalidEmailOrVerificationCode": { + "message": "Invalid email or verification code" + }, "valueCopied": { "message": "Skopiowano $VALUE$", "description": "Value has been copied to the clipboard.", @@ -2857,6 +2860,9 @@ "reviewAtRiskLoginSlideImgAltPeriod": { "message": "Ilustracja listy danych logowania, które są zagrożone." }, + "welcomeDialogGraphicAlt": { + "message": "Illustration of the layout of the Bitwarden vault page." + }, "generatePasswordSlideDesc": { "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" @@ -3080,6 +3086,45 @@ } } }, + "durationTimeHours": { + "message": "$HOURS$ hours", + "placeholders": { + "hours": { + "content": "$1", + "example": "5" + } + } + }, + "sendCreatedDescriptionV2": { + "message": "Copy and share this Send link. The Send will be available to anyone with the link for the next $TIME$.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated.", + "placeholders": { + "time": { + "content": "$1", + "example": "7 days, 1 hour, 1 day" + } + } + }, + "sendCreatedDescriptionPassword": { + "message": "Copy and share this Send link. The Send will be available to anyone with the link and password you set for the next $TIME$.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated.", + "placeholders": { + "time": { + "content": "$1", + "example": "7 days, 1 hour, 1 day" + } + } + }, + "sendCreatedDescriptionEmail": { + "message": "Copy and share this Send link. It can be viewed by the people you specified for the next $TIME$.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated.", + "placeholders": { + "time": { + "content": "$1", + "example": "7 days, 1 hour, 1 day" + } + } + }, "sendLinkCopied": { "message": "Link wysyłki został skopiowany", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." @@ -6121,6 +6166,9 @@ "enterMultipleEmailsSeparatedByComma": { "message": "Enter multiple emails by separating with a comma." }, + "emailsRequiredChangeAccessType": { + "message": "Email verification requires at least one email address. To remove all emails, change the access type above." + }, "emailPlaceholder": { "message": "user@bitwarden.com , user@acme.com" }, @@ -6133,5 +6181,8 @@ "sendPasswordHelperText": { "message": "Individuals will need to enter the password to view this Send", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "userVerificationFailed": { + "message": "User verification failed." } } diff --git a/apps/browser/src/_locales/pt_BR/messages.json b/apps/browser/src/_locales/pt_BR/messages.json index 679173205b1..9b0c2483b2a 100644 --- a/apps/browser/src/_locales/pt_BR/messages.json +++ b/apps/browser/src/_locales/pt_BR/messages.json @@ -574,10 +574,10 @@ "message": "Os itens arquivados aparecerão aqui e serão excluídos dos resultados gerais de busca e das sugestões de preenchimento automático." }, "itemArchiveToast": { - "message": "Item archived" + "message": "Item arquivado" }, "itemUnarchivedToast": { - "message": "Item unarchived" + "message": "Item desarquivado" }, "archiveItem": { "message": "Arquivar item" @@ -896,6 +896,9 @@ "invalidVerificationCode": { "message": "Código de verificação inválido" }, + "invalidEmailOrVerificationCode": { + "message": "E-mail ou código de verificação inválido" + }, "valueCopied": { "message": "$VALUE$ copiado(a)", "description": "Value has been copied to the clipboard.", @@ -988,10 +991,10 @@ "message": "Não" }, "noAuth": { - "message": "Anyone with the link" + "message": "Qualquer um com o link" }, "anyOneWithPassword": { - "message": "Anyone with a password set by you" + "message": "Qualquer pessoa com uma senha configurada por você" }, "location": { "message": "Localização" @@ -2052,7 +2055,7 @@ "message": "E-mail" }, "emails": { - "message": "Emails" + "message": "E-mails" }, "phone": { "message": "Telefone" @@ -2857,6 +2860,9 @@ "reviewAtRiskLoginSlideImgAltPeriod": { "message": "Ilustração de uma lista de credenciais em risco." }, + "welcomeDialogGraphicAlt": { + "message": "Ilustração do layout da página do Cofre do Bitwarden." + }, "generatePasswordSlideDesc": { "message": "Gere uma senha forte e única com rapidez com o menu de preenchimento automático no site em risco.", "description": "Description of the generate password slide on the at-risk password page carousel" @@ -3080,6 +3086,45 @@ } } }, + "durationTimeHours": { + "message": "$HOURS$ horas", + "placeholders": { + "hours": { + "content": "$1", + "example": "5" + } + } + }, + "sendCreatedDescriptionV2": { + "message": "Copie e compartilhe este link do Send. O Send ficará disponível para qualquer um com o link por $TIME$.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated.", + "placeholders": { + "time": { + "content": "$1", + "example": "7 days, 1 hour, 1 day" + } + } + }, + "sendCreatedDescriptionPassword": { + "message": "Copie e compartilhe este link do Send. O Send ficará disponível para qualquer um com o link e a senha por $TIME$.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated.", + "placeholders": { + "time": { + "content": "$1", + "example": "7 days, 1 hour, 1 day" + } + } + }, + "sendCreatedDescriptionEmail": { + "message": "Copie e compartilhe este link do Send. Ele pode ser visto pelas pessoas que você especificou pelos próximos $TIME$.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated.", + "placeholders": { + "time": { + "content": "$1", + "example": "7 days, 1 hour, 1 day" + } + } + }, "sendLinkCopied": { "message": "Link do Send copiado", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." @@ -5961,7 +6006,7 @@ "message": "Número do cartão" }, "errorCannotDecrypt": { - "message": "Error: Cannot decrypt" + "message": "Erro: Não é possível descriptografar" }, "removeMasterPasswordForOrgUserKeyConnector": { "message": "A sua organização não está mais usando senhas principais para se conectar ao Bitwarden. Para continuar, verifique a organização e o domínio." @@ -6101,37 +6146,43 @@ "message": "Por que estou vendo isso?" }, "items": { - "message": "Items" + "message": "Itens" }, "searchResults": { - "message": "Search results" + "message": "Resultados da busca" }, "resizeSideNavigation": { "message": "Redimensionar navegação lateral" }, "whoCanView": { - "message": "Who can view" + "message": "Quem pode visualizar" }, "specificPeople": { - "message": "Specific people" + "message": "Pessoas específicas" }, "emailVerificationDesc": { - "message": "After sharing this Send link, individuals will need to verify their email with a code to view this Send." + "message": "Após compartilhar este link de Send, indivíduos precisarão verificar seus e-mails com um código para visualizar este Send." }, "enterMultipleEmailsSeparatedByComma": { - "message": "Enter multiple emails by separating with a comma." + "message": "Digite vários e-mails, separados com uma vírgula." + }, + "emailsRequiredChangeAccessType": { + "message": "A verificação de e-mail requer pelo menos um endereço de e-mail. Para remover todos, altere o tipo de acesso acima." }, "emailPlaceholder": { - "message": "user@bitwarden.com , user@acme.com" + "message": "usuário@bitwarden.com , usuário@acme.com" }, "downloadBitwardenApps": { - "message": "Download Bitwarden apps" + "message": "Baixar aplicativos do Bitwarden" }, "emailProtected": { - "message": "Email protected" + "message": "Protegido por e-mail" }, "sendPasswordHelperText": { - "message": "Individuals will need to enter the password to view this Send", + "message": "Os indivíduos precisarão digitar a senha para ver este Send", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "userVerificationFailed": { + "message": "Falha na verificação do usuário." } } diff --git a/apps/browser/src/_locales/pt_PT/messages.json b/apps/browser/src/_locales/pt_PT/messages.json index 9094e04094d..5d498908fa5 100644 --- a/apps/browser/src/_locales/pt_PT/messages.json +++ b/apps/browser/src/_locales/pt_PT/messages.json @@ -896,6 +896,9 @@ "invalidVerificationCode": { "message": "Código de verificação inválido" }, + "invalidEmailOrVerificationCode": { + "message": "E-mail ou código de verificação inválido" + }, "valueCopied": { "message": "$VALUE$ copiado(a)", "description": "Value has been copied to the clipboard.", @@ -2857,6 +2860,9 @@ "reviewAtRiskLoginSlideImgAltPeriod": { "message": "Ilustração de uma lista de credenciais que estão em risco." }, + "welcomeDialogGraphicAlt": { + "message": "Ilustração do layout da página do cofre Bitwarden." + }, "generatePasswordSlideDesc": { "message": "Gira rapidamente uma palavra-passe forte e única com o menu de preenchimento automático do Bitwarden no site em risco.", "description": "Description of the generate password slide on the at-risk password page carousel" @@ -3080,6 +3086,45 @@ } } }, + "durationTimeHours": { + "message": "$HOURS$ horas", + "placeholders": { + "hours": { + "content": "$1", + "example": "5" + } + } + }, + "sendCreatedDescriptionV2": { + "message": "Copie e partilhe este link do Send. O Send estará disponível para qualquer pessoa com o link durante os próximos $TIME$.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated.", + "placeholders": { + "time": { + "content": "$1", + "example": "7 days, 1 hour, 1 day" + } + } + }, + "sendCreatedDescriptionPassword": { + "message": "Copie e partilhe este link do Send. O Send estará disponível para qualquer pessoa com o link e palavras-passe durante os próximos $TIME$.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated.", + "placeholders": { + "time": { + "content": "$1", + "example": "7 days, 1 hour, 1 day" + } + } + }, + "sendCreatedDescriptionEmail": { + "message": "Copie e partilhe este link do Send. Pode ser visualizado pelas pessoas que especificou durante os próximos $TIME$.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated.", + "placeholders": { + "time": { + "content": "$1", + "example": "7 days, 1 hour, 1 day" + } + } + }, "sendLinkCopied": { "message": "Link do Send copiado", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." @@ -6121,6 +6166,9 @@ "enterMultipleEmailsSeparatedByComma": { "message": "Introduza vários e-mails, separados por vírgula." }, + "emailsRequiredChangeAccessType": { + "message": "A verificação por e-mail requer pelo menos um endereço de e-mail. Para remover todos os e-mails, altere o tipo de acesso acima." + }, "emailPlaceholder": { "message": "utilizador@bitwarden.com , utilizador@acme.com" }, @@ -6133,5 +6181,8 @@ "sendPasswordHelperText": { "message": "Os indivíduos terão de introduzir a palavra-passe para ver este Send", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "userVerificationFailed": { + "message": "Falha na verificação do utilizador." } } diff --git a/apps/browser/src/_locales/ro/messages.json b/apps/browser/src/_locales/ro/messages.json index 47f7ae9cae3..eb6c7eb2a66 100644 --- a/apps/browser/src/_locales/ro/messages.json +++ b/apps/browser/src/_locales/ro/messages.json @@ -896,6 +896,9 @@ "invalidVerificationCode": { "message": "Cod de verificare nevalid" }, + "invalidEmailOrVerificationCode": { + "message": "Invalid email or verification code" + }, "valueCopied": { "message": " $VALUE$ s-a copiat", "description": "Value has been copied to the clipboard.", @@ -2857,6 +2860,9 @@ "reviewAtRiskLoginSlideImgAltPeriod": { "message": "Illustration of a list of logins that are at-risk." }, + "welcomeDialogGraphicAlt": { + "message": "Illustration of the layout of the Bitwarden vault page." + }, "generatePasswordSlideDesc": { "message": "Quickly generate a strong, unique password with the Bitwarden autofill menu on the at-risk site.", "description": "Description of the generate password slide on the at-risk password page carousel" @@ -3080,6 +3086,45 @@ } } }, + "durationTimeHours": { + "message": "$HOURS$ hours", + "placeholders": { + "hours": { + "content": "$1", + "example": "5" + } + } + }, + "sendCreatedDescriptionV2": { + "message": "Copy and share this Send link. The Send will be available to anyone with the link for the next $TIME$.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated.", + "placeholders": { + "time": { + "content": "$1", + "example": "7 days, 1 hour, 1 day" + } + } + }, + "sendCreatedDescriptionPassword": { + "message": "Copy and share this Send link. The Send will be available to anyone with the link and password you set for the next $TIME$.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated.", + "placeholders": { + "time": { + "content": "$1", + "example": "7 days, 1 hour, 1 day" + } + } + }, + "sendCreatedDescriptionEmail": { + "message": "Copy and share this Send link. It can be viewed by the people you specified for the next $TIME$.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated.", + "placeholders": { + "time": { + "content": "$1", + "example": "7 days, 1 hour, 1 day" + } + } + }, "sendLinkCopied": { "message": "Send link copied", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." @@ -6121,6 +6166,9 @@ "enterMultipleEmailsSeparatedByComma": { "message": "Enter multiple emails by separating with a comma." }, + "emailsRequiredChangeAccessType": { + "message": "Email verification requires at least one email address. To remove all emails, change the access type above." + }, "emailPlaceholder": { "message": "user@bitwarden.com , user@acme.com" }, @@ -6133,5 +6181,8 @@ "sendPasswordHelperText": { "message": "Individuals will need to enter the password to view this Send", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "userVerificationFailed": { + "message": "User verification failed." } } diff --git a/apps/browser/src/_locales/ru/messages.json b/apps/browser/src/_locales/ru/messages.json index d1fb3de89a6..75a2afb4e1a 100644 --- a/apps/browser/src/_locales/ru/messages.json +++ b/apps/browser/src/_locales/ru/messages.json @@ -896,6 +896,9 @@ "invalidVerificationCode": { "message": "Неверный код подтверждения" }, + "invalidEmailOrVerificationCode": { + "message": "Неверный email или код подтверждения" + }, "valueCopied": { "message": "$VALUE$ скопировано", "description": "Value has been copied to the clipboard.", @@ -2857,6 +2860,9 @@ "reviewAtRiskLoginSlideImgAltPeriod": { "message": "Иллюстрация списка логинов, которые подвержены риску." }, + "welcomeDialogGraphicAlt": { + "message": "Иллюстрация макета страницы хранилища Bitwarden." + }, "generatePasswordSlideDesc": { "message": "Быстро сгенерируйте надежный уникальный пароль с помощью меню автозаполнения Bitwarden на сайте, подверженном риску.", "description": "Description of the generate password slide on the at-risk password page carousel" @@ -3080,6 +3086,45 @@ } } }, + "durationTimeHours": { + "message": "$HOURS$ час.", + "placeholders": { + "hours": { + "content": "$1", + "example": "5" + } + } + }, + "sendCreatedDescriptionV2": { + "message": "Скопируйте и поделитесь этой ссылкой Send. Send будет доступна всем, у кого есть ссылка, в течение следующих $TIME$.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated.", + "placeholders": { + "time": { + "content": "$1", + "example": "7 days, 1 hour, 1 day" + } + } + }, + "sendCreatedDescriptionPassword": { + "message": "Скопируйте и поделитесь этой ссылкой Send. Send будет доступна всем, у кого есть ссылка и пароль в течение следующих $TIME$.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated.", + "placeholders": { + "time": { + "content": "$1", + "example": "7 days, 1 hour, 1 day" + } + } + }, + "sendCreatedDescriptionEmail": { + "message": "Скопируйте и распространите эту ссылку для Send. Она может быть просмотрена указанными вами пользователями в течение $TIME$.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated.", + "placeholders": { + "time": { + "content": "$1", + "example": "7 days, 1 hour, 1 day" + } + } + }, "sendLinkCopied": { "message": "Ссылка на Send скопирована", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." @@ -6121,6 +6166,9 @@ "enterMultipleEmailsSeparatedByComma": { "message": "Введите несколько email, разделяя их запятой." }, + "emailsRequiredChangeAccessType": { + "message": "Для проверки электронной почты требуется как минимум один адрес email. Чтобы удалить все адреса электронной почты, измените тип доступа выше." + }, "emailPlaceholder": { "message": "user@bitwarden.com , user@acme.com" }, @@ -6133,5 +6181,8 @@ "sendPasswordHelperText": { "message": "Пользователям необходимо будет ввести пароль для просмотра этой Send", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "userVerificationFailed": { + "message": "Проверка пользователя не удалась." } } diff --git a/apps/browser/src/_locales/si/messages.json b/apps/browser/src/_locales/si/messages.json index e70c620eaf8..d7e63d70f87 100644 --- a/apps/browser/src/_locales/si/messages.json +++ b/apps/browser/src/_locales/si/messages.json @@ -896,6 +896,9 @@ "invalidVerificationCode": { "message": "වලංගු නොවන සත්යාපන කේතය" }, + "invalidEmailOrVerificationCode": { + "message": "Invalid email or verification code" + }, "valueCopied": { "message": "$VALUE$ පිටපත් කරන ලදි", "description": "Value has been copied to the clipboard.", @@ -2857,6 +2860,9 @@ "reviewAtRiskLoginSlideImgAltPeriod": { "message": "Illustration of a list of logins that are at-risk." }, + "welcomeDialogGraphicAlt": { + "message": "Illustration of the layout of the Bitwarden vault page." + }, "generatePasswordSlideDesc": { "message": "Quickly generate a strong, unique password with the Bitwarden autofill menu on the at-risk site.", "description": "Description of the generate password slide on the at-risk password page carousel" @@ -3080,6 +3086,45 @@ } } }, + "durationTimeHours": { + "message": "$HOURS$ hours", + "placeholders": { + "hours": { + "content": "$1", + "example": "5" + } + } + }, + "sendCreatedDescriptionV2": { + "message": "Copy and share this Send link. The Send will be available to anyone with the link for the next $TIME$.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated.", + "placeholders": { + "time": { + "content": "$1", + "example": "7 days, 1 hour, 1 day" + } + } + }, + "sendCreatedDescriptionPassword": { + "message": "Copy and share this Send link. The Send will be available to anyone with the link and password you set for the next $TIME$.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated.", + "placeholders": { + "time": { + "content": "$1", + "example": "7 days, 1 hour, 1 day" + } + } + }, + "sendCreatedDescriptionEmail": { + "message": "Copy and share this Send link. It can be viewed by the people you specified for the next $TIME$.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated.", + "placeholders": { + "time": { + "content": "$1", + "example": "7 days, 1 hour, 1 day" + } + } + }, "sendLinkCopied": { "message": "Send link copied", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." @@ -6121,6 +6166,9 @@ "enterMultipleEmailsSeparatedByComma": { "message": "Enter multiple emails by separating with a comma." }, + "emailsRequiredChangeAccessType": { + "message": "Email verification requires at least one email address. To remove all emails, change the access type above." + }, "emailPlaceholder": { "message": "user@bitwarden.com , user@acme.com" }, @@ -6133,5 +6181,8 @@ "sendPasswordHelperText": { "message": "Individuals will need to enter the password to view this Send", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "userVerificationFailed": { + "message": "User verification failed." } } diff --git a/apps/browser/src/_locales/sk/messages.json b/apps/browser/src/_locales/sk/messages.json index e1886098a31..806e83e0b1f 100644 --- a/apps/browser/src/_locales/sk/messages.json +++ b/apps/browser/src/_locales/sk/messages.json @@ -896,6 +896,9 @@ "invalidVerificationCode": { "message": "Neplatný verifikačný kód" }, + "invalidEmailOrVerificationCode": { + "message": "Neplatný e-mailový alebo overovací kód" + }, "valueCopied": { "message": " skopírované", "description": "Value has been copied to the clipboard.", @@ -2857,6 +2860,9 @@ "reviewAtRiskLoginSlideImgAltPeriod": { "message": "Príklady zoznamu prihlásení, ktoré sú ohrozené." }, + "welcomeDialogGraphicAlt": { + "message": "Ilustrácia rozloženia stránky trezoru Bitwarden." + }, "generatePasswordSlideDesc": { "message": "Rýchlo generujte silné, jedinečné heslo pomocu ponuky automatického vypĺňania Bitwardenu na ohrozených stránkach.", "description": "Description of the generate password slide on the at-risk password page carousel" @@ -3080,6 +3086,45 @@ } } }, + "durationTimeHours": { + "message": "$HOURS$ hod.", + "placeholders": { + "hours": { + "content": "$1", + "example": "5" + } + } + }, + "sendCreatedDescriptionV2": { + "message": "Skopírujte a zdieľajte tento odkaz na Send. Send bude dostupný každému, kto má odkaz, počas nasledujúcich $TIME$.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated.", + "placeholders": { + "time": { + "content": "$1", + "example": "7 days, 1 hour, 1 day" + } + } + }, + "sendCreatedDescriptionPassword": { + "message": "Skopírujte a zdieľajte tento odkaz na Send. Send bude dostupný každému, kto má odkaz a heslo od vás, počas nasledujúcich $TIME$.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated.", + "placeholders": { + "time": { + "content": "$1", + "example": "7 days, 1 hour, 1 day" + } + } + }, + "sendCreatedDescriptionEmail": { + "message": "Skopírujte a zdieľajte tento odkaz na Send. Zobraziť ho môžu ľudia, ktorých ste vybrali, počas nasledujúcich $TIME$.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated.", + "placeholders": { + "time": { + "content": "$1", + "example": "7 days, 1 hour, 1 day" + } + } + }, "sendLinkCopied": { "message": "Skopírovaný odkaz na Send", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." @@ -6121,6 +6166,9 @@ "enterMultipleEmailsSeparatedByComma": { "message": "Zadajte viacero e-mailových adries oddelených čiarkou." }, + "emailsRequiredChangeAccessType": { + "message": "Overenie e-mailu vyžaduje aspoň jednu e-mailovú adresu. Ak chcete odstrániť všetky e-maily, zmeňte typ prístupu vyššie." + }, "emailPlaceholder": { "message": "pouzivate@bitwarden.com, pouzivatel@acme.com" }, @@ -6133,5 +6181,8 @@ "sendPasswordHelperText": { "message": "Jednotlivci budú musieť zadať heslo, aby mohli zobraziť tento Send", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "userVerificationFailed": { + "message": "Overenie používateľa zlyhalo." } } diff --git a/apps/browser/src/_locales/sl/messages.json b/apps/browser/src/_locales/sl/messages.json index 100a04a3012..88f54663ccd 100644 --- a/apps/browser/src/_locales/sl/messages.json +++ b/apps/browser/src/_locales/sl/messages.json @@ -6,30 +6,30 @@ "message": "Bitwarden logo" }, "extName": { - "message": "Bitwarden Password Manager", + "message": "Bitwarden – Upravitelj gesel", "description": "Extension name, MUST be less than 40 characters (Safari restriction)" }, "extDesc": { - "message": "At home, at work, or on the go, Bitwarden easily secures all your passwords, passkeys, and sensitive information", + "message": "Doma, na delu ali na poti – Bitwarden enostavno zaščiti vsa vaša gesla, ključe za dostop in občutljive podatke", "description": "Extension description, MUST be less than 112 characters (Safari restriction)" }, "loginOrCreateNewAccount": { "message": "Prijavite se ali ustvarite nov račun za dostop do svojega varnega trezorja." }, "inviteAccepted": { - "message": "Invitation accepted" + "message": "Povabilo sprejeto" }, "createAccount": { "message": "Ustvari račun" }, "newToBitwarden": { - "message": "New to Bitwarden?" + "message": "Novi na Bitwardenu?" }, "logInWithPasskey": { - "message": "Log in with passkey" + "message": "Prijava s ključem za dostop" }, "unlockWithPasskey": { - "message": "Unlock with passkey" + "message": "Odkleni s ključem za dostop" }, "useSingleSignOn": { "message": "Use single sign-on" @@ -38,10 +38,10 @@ "message": "Your organization requires single sign-on." }, "welcomeBack": { - "message": "Welcome back" + "message": "Dobrodošli nazaj" }, "setAStrongPassword": { - "message": "Set a strong password" + "message": "Nastavite močno geslo" }, "finishCreatingYourAccountBySettingAPassword": { "message": "Finish creating your account by setting a password" @@ -90,7 +90,7 @@ "message": "Namig za glavno geslo (neobvezno)" }, "passwordStrengthScore": { - "message": "Password strength score $SCORE$", + "message": "Ocena moči gesla: $SCORE$", "placeholders": { "score": { "content": "$1", @@ -99,7 +99,7 @@ } }, "joinOrganization": { - "message": "Join organization" + "message": "Pridružite se organizaciji" }, "joinOrganizationName": { "message": "Join $ORGANIZATIONNAME$", @@ -156,31 +156,31 @@ "message": "Kopiraj varnostno kodo" }, "copyName": { - "message": "Copy name" + "message": "Kopiraj ime" }, "copyCompany": { - "message": "Copy company" + "message": "Kopiraj podjetje" }, "copySSN": { "message": "Copy Social Security number" }, "copyPassportNumber": { - "message": "Copy passport number" + "message": "Kopiraj številko potnega lista" }, "copyLicenseNumber": { "message": "Copy license number" }, "copyPrivateKey": { - "message": "Copy private key" + "message": "Kopiraj zasebni ključ" }, "copyPublicKey": { - "message": "Copy public key" + "message": "Kopiraj javni ključ" }, "copyFingerprint": { "message": "Copy fingerprint" }, "copyCustomField": { - "message": "Copy $FIELD$", + "message": "Kopiraj $FIELD$", "placeholders": { "field": { "content": "$1", @@ -189,17 +189,17 @@ } }, "copyWebsite": { - "message": "Copy website" + "message": "Kopiraj spletno stran" }, "copyNotes": { - "message": "Copy notes" + "message": "Kopiraj zapiske" }, "copy": { - "message": "Copy", + "message": "Kopiraj", "description": "Copy to clipboard" }, "fill": { - "message": "Fill", + "message": "Izpolni", "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": { @@ -215,10 +215,10 @@ "message": "Samodejno izpolni identiteto" }, "fillVerificationCode": { - "message": "Fill verification code" + "message": "Izpolni kodo za preverjanje" }, "fillVerificationCodeAria": { - "message": "Fill Verification Code", + "message": "Izpolni kodo za preverjanje", "description": "Aria label for the heading displayed the inline menu for totp code autofill" }, "generatePasswordCopied": { @@ -297,13 +297,13 @@ "message": "Spremeni glavno geslo" }, "continueToWebApp": { - "message": "Continue to web app?" + "message": "Nadaljuj v spletno aplikacijo?" }, "continueToWebAppDesc": { - "message": "Explore more features of your Bitwarden account on the web app." + "message": "Raziščite več funkcij vašega Bitwarden računa na spletni aplikaciji." }, "continueToHelpCenter": { - "message": "Continue to Help Center?" + "message": "Nadaljuj na center za pomoč?" }, "continueToHelpCenterDesc": { "message": "Learn more about how to use Bitwarden on the Help Center." @@ -315,7 +315,7 @@ "message": "Help others find out if Bitwarden is right for them. Visit your browser's extension store and leave a rating now." }, "changeMasterPasswordOnWebConfirmation": { - "message": "You can change your master password on the Bitwarden web app." + "message": "Vaše glavno geslo lahko zamenjate v Bitwarden spletni aplikaciji." }, "fingerprintPhrase": { "message": "Identifikacijsko geslo", @@ -332,19 +332,19 @@ "message": "Odjava" }, "aboutBitwarden": { - "message": "About Bitwarden" + "message": "O Bitwardenu" }, "about": { "message": "O programu" }, "moreFromBitwarden": { - "message": "More from Bitwarden" + "message": "Več od Bitwardena" }, "continueToBitwardenDotCom": { - "message": "Continue to bitwarden.com?" + "message": "Nadaljuj na bitwarden.com?" }, "bitwardenForBusiness": { - "message": "Bitwarden for Business" + "message": "Bitwarden za podjetja" }, "bitwardenAuthenticator": { "message": "Bitwarden Authenticator" @@ -398,10 +398,10 @@ } }, "newFolder": { - "message": "New folder" + "message": "Nova mapa" }, "folderName": { - "message": "Folder name" + "message": "Ime mape" }, "folderHintText": { "message": "Nest a folder by adding the parent folder's name followed by a “/”. Example: Social/Forums" @@ -440,7 +440,7 @@ "message": "Sinhronizacija" }, "syncNow": { - "message": "Sync now" + "message": "Sinhroniziraj zdaj" }, "lastSync": { "message": "Zadnja sinhronizacija:" @@ -456,7 +456,7 @@ "message": "Avtomatično generiraj močna, edinstvena gesla za vaše prijave." }, "bitWebVaultApp": { - "message": "Bitwarden web app" + "message": "Bitwarden spletna aplikacija" }, "select": { "message": "Izberi" @@ -468,7 +468,7 @@ "message": "Generate passphrase" }, "passwordGenerated": { - "message": "Password generated" + "message": "Geslo generirano" }, "passphraseGenerated": { "message": "Passphrase generated" @@ -493,7 +493,7 @@ "description": "Card header for password generator include block" }, "uppercaseDescription": { - "message": "Include uppercase characters", + "message": "Vključi velike črke", "description": "Tooltip for the password generator uppercase character checkbox" }, "uppercaseLabel": { @@ -501,7 +501,7 @@ "description": "Label for the password generator uppercase character checkbox" }, "lowercaseDescription": { - "message": "Include lowercase characters", + "message": "Vključi male črke", "description": "Full description for the password generator lowercase character checkbox" }, "lowercaseLabel": { @@ -509,7 +509,7 @@ "description": "Label for the password generator lowercase character checkbox" }, "numbersDescription": { - "message": "Include numbers", + "message": "Vključi števila", "description": "Full description for the password generator numbers checkbox" }, "numbersLabel": { @@ -517,7 +517,7 @@ "description": "Label for the password generator numbers checkbox" }, "specialCharactersDescription": { - "message": "Include special characters", + "message": "Vključi posebne znake", "description": "Full description for the password generator special characters checkbox" }, "numWords": { @@ -540,7 +540,7 @@ "message": "Minimalno posebnih znakov" }, "avoidAmbiguous": { - "message": "Avoid ambiguous characters", + "message": "Izogibaj se dvoumnim znakom", "description": "Label for the avoid ambiguous characters checkbox." }, "generatorPolicyInEffect": { @@ -554,21 +554,21 @@ "message": "Reset search" }, "archiveNoun": { - "message": "Archive", + "message": "Arhiv", "description": "Noun" }, "archiveVerb": { - "message": "Archive", + "message": "Arhiviraj", "description": "Verb" }, "unArchive": { - "message": "Unarchive" + "message": "Odstrani iz arhiva" }, "itemsInArchive": { - "message": "Items in archive" + "message": "Elementi v arhivu" }, "noItemsInArchive": { - "message": "No items in archive" + "message": "Ni elementov v arhivu" }, "noItemsInArchiveDesc": { "message": "Archived items will appear here and will be excluded from general search results and autofill suggestions." @@ -592,10 +592,10 @@ "message": "Unarchive and save" }, "upgradeToUseArchive": { - "message": "A premium membership is required to use Archive." + "message": "Za uporabo Arhiva je potrebno premium članstvo." }, "itemRestored": { - "message": "Item has been restored" + "message": "Vnos je bil obnovljen" }, "edit": { "message": "Uredi" @@ -604,13 +604,13 @@ "message": "Pogled" }, "viewAll": { - "message": "View all" + "message": "Poglej vse" }, "showAll": { - "message": "Show all" + "message": "Prikaži vse" }, "viewLess": { - "message": "View less" + "message": "Poglej manj" }, "viewLogin": { "message": "View login" @@ -640,10 +640,10 @@ "message": "Unfavorite" }, "itemAddedToFavorites": { - "message": "Item added to favorites" + "message": "Element dodan med priljubljene" }, "itemRemovedFromFavorites": { - "message": "Item removed from favorites" + "message": "Element odstranen iz priljubljenih" }, "notes": { "message": "Opombe" @@ -709,7 +709,7 @@ "message": "Vault timeout" }, "otherOptions": { - "message": "Other options" + "message": "Ostale možnosti" }, "rateExtension": { "message": "Ocenite to razširitev" @@ -718,25 +718,25 @@ "message": "Vaš brskalnik ne podpira enostavnega kopiranja na odložišče. Prosimo, kopirajte ročno." }, "verifyYourIdentity": { - "message": "Verify your identity" + "message": "Potrdite vašo identiteto" }, "weDontRecognizeThisDevice": { - "message": "We don't recognize this device. Enter the code sent to your email to verify your identity." + "message": "Te naprave ne prepoznamo. Vnesite kodo, ki je bila poslana na vaš e-poštni naslov, da potrdite vašo identiteto." }, "continueLoggingIn": { - "message": "Continue logging in" + "message": "Nadaljujte s prijavo" }, "yourVaultIsLocked": { "message": "Vaš trezor je zaklenjen. Za nadaljevanje potrdite svojo identiteto." }, "yourVaultIsLockedV2": { - "message": "Your vault is locked" + "message": "Vaš trezor je zaklenjen" }, "yourAccountIsLocked": { - "message": "Your account is locked" + "message": "Vaš račun je zaklenjen" }, "or": { - "message": "or" + "message": "ali" }, "unlock": { "message": "Odkleni" @@ -758,7 +758,7 @@ "message": "Napačno glavno geslo" }, "invalidMasterPasswordConfirmEmailAndHost": { - "message": "Invalid master password. Confirm your email is correct and your account was created on $HOST$.", + "message": "Neveljavno glavno geslo. Preverite, da je vaš e-poštni naslov pravilen, ter da je bil vaš račun ustvarjen na $HOST$.", "placeholders": { "host": { "content": "$1", @@ -776,7 +776,7 @@ "message": "Zakleni zdaj" }, "lockAll": { - "message": "Lock all" + "message": "Zakleni vse" }, "immediately": { "message": "Takoj" @@ -833,13 +833,13 @@ "message": "Confirm master password" }, "masterPassword": { - "message": "Master password" + "message": "Glavno geslo" }, "masterPassImportant": { - "message": "Your master password cannot be recovered if you forget it!" + "message": "Če pozabite glavno geslo, ga ne bo mogoče obnoviti!" }, "masterPassHintLabel": { - "message": "Master password hint" + "message": "Namig za glavno geslo" }, "errorOccurred": { "message": "Prišlo je do napake" @@ -876,7 +876,7 @@ "message": "Your new account has been created!" }, "youHaveBeenLoggedIn": { - "message": "You have been logged in!" + "message": "Prijavljeni ste!" }, "youSuccessfullyLoggedIn": { "message": "You successfully logged in" @@ -896,6 +896,9 @@ "invalidVerificationCode": { "message": "Neveljavna koda za preverjanje" }, + "invalidEmailOrVerificationCode": { + "message": "Invalid email or verification code" + }, "valueCopied": { "message": "$VALUE$ kopirana", "description": "Value has been copied to the clipboard.", @@ -970,7 +973,7 @@ "message": "Restart registration" }, "expiredLink": { - "message": "Expired link" + "message": "Pretečena povezava" }, "pleaseRestartRegistrationOrTryLoggingIn": { "message": "Please restart registration or try logging in." @@ -994,7 +997,7 @@ "message": "Anyone with a password set by you" }, "location": { - "message": "Location" + "message": "Lokacija" }, "unexpectedError": { "message": "Prišlo je do nepričakovane napake." @@ -1009,10 +1012,10 @@ "message": "Avtentikacija v dveh korakih dodatno varuje vaš račun, saj zahteva, da vsakokratno prijavo potrdite z drugo napravo, kot je varnostni ključ, aplikacija za preverjanje pristnosti, SMS, telefonski klic ali e-pošta. Avtentikacijo v dveh korakih lahko omogočite v spletnem trezorju bitwarden.com. Ali želite spletno stran obiskati sedaj?" }, "twoStepLoginConfirmationContent": { - "message": "Make your account more secure by setting up two-step login in the Bitwarden web app." + "message": "Zavarujte vaš račun tako, da nastavite dvostopenjsko prijavo v Bitwarden spletni aplikaciji." }, "twoStepLoginConfirmationTitle": { - "message": "Continue to web app?" + "message": "Nadaljuj v spletno aplikacijo?" }, "editedFolder": { "message": "Mapa shranjena" @@ -1055,7 +1058,7 @@ "message": "Nov URI" }, "addDomain": { - "message": "Add domain", + "message": "Dodaj domeno", "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": { @@ -2857,6 +2860,9 @@ "reviewAtRiskLoginSlideImgAltPeriod": { "message": "Illustration of a list of logins that are at-risk." }, + "welcomeDialogGraphicAlt": { + "message": "Illustration of the layout of the Bitwarden vault page." + }, "generatePasswordSlideDesc": { "message": "Quickly generate a strong, unique password with the Bitwarden autofill menu on the at-risk site.", "description": "Description of the generate password slide on the at-risk password page carousel" @@ -3080,6 +3086,45 @@ } } }, + "durationTimeHours": { + "message": "$HOURS$ hours", + "placeholders": { + "hours": { + "content": "$1", + "example": "5" + } + } + }, + "sendCreatedDescriptionV2": { + "message": "Copy and share this Send link. The Send will be available to anyone with the link for the next $TIME$.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated.", + "placeholders": { + "time": { + "content": "$1", + "example": "7 days, 1 hour, 1 day" + } + } + }, + "sendCreatedDescriptionPassword": { + "message": "Copy and share this Send link. The Send will be available to anyone with the link and password you set for the next $TIME$.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated.", + "placeholders": { + "time": { + "content": "$1", + "example": "7 days, 1 hour, 1 day" + } + } + }, + "sendCreatedDescriptionEmail": { + "message": "Copy and share this Send link. It can be viewed by the people you specified for the next $TIME$.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated.", + "placeholders": { + "time": { + "content": "$1", + "example": "7 days, 1 hour, 1 day" + } + } + }, "sendLinkCopied": { "message": "Send link copied", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." @@ -6062,7 +6107,7 @@ "message": "Contact your admin to regain access." }, "leaveConfirmationDialogConfirmButton": { - "message": "Leave $ORGANIZATION$", + "message": "Zapusti $ORGANIZATION$", "placeholders": { "organization": { "content": "$1", @@ -6071,10 +6116,10 @@ } }, "howToManageMyVault": { - "message": "How do I manage my vault?" + "message": "Kako uporabljam svoj trezor?" }, "transferItemsToOrganizationTitle": { - "message": "Transfer items to $ORGANIZATION$", + "message": "Prenesi elemente v $ORGANIZATION$", "placeholders": { "organization": { "content": "$1", @@ -6092,19 +6137,19 @@ } }, "acceptTransfer": { - "message": "Accept transfer" + "message": "Sprejmi prenos" }, "declineAndLeave": { "message": "Decline and leave" }, "whyAmISeeingThis": { - "message": "Why am I seeing this?" + "message": "Zakaj se mi to prikazuje?" }, "items": { - "message": "Items" + "message": "Elementi" }, "searchResults": { - "message": "Search results" + "message": "Rezultati iskanja" }, "resizeSideNavigation": { "message": "Resize side navigation" @@ -6121,11 +6166,14 @@ "enterMultipleEmailsSeparatedByComma": { "message": "Enter multiple emails by separating with a comma." }, + "emailsRequiredChangeAccessType": { + "message": "Email verification requires at least one email address. To remove all emails, change the access type above." + }, "emailPlaceholder": { - "message": "user@bitwarden.com , user@acme.com" + "message": "uporabnik@bitwarden.com , uporabnik@acme.com" }, "downloadBitwardenApps": { - "message": "Download Bitwarden apps" + "message": "Prenesi Bitwarden aplikacije" }, "emailProtected": { "message": "Email protected" @@ -6133,5 +6181,8 @@ "sendPasswordHelperText": { "message": "Individuals will need to enter the password to view this Send", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "userVerificationFailed": { + "message": "User verification failed." } } diff --git a/apps/browser/src/_locales/sr/messages.json b/apps/browser/src/_locales/sr/messages.json index e91e003c8e0..b375ca5d536 100644 --- a/apps/browser/src/_locales/sr/messages.json +++ b/apps/browser/src/_locales/sr/messages.json @@ -896,6 +896,9 @@ "invalidVerificationCode": { "message": "Неисправан верификациони код" }, + "invalidEmailOrVerificationCode": { + "message": "Invalid email or verification code" + }, "valueCopied": { "message": "$VALUE$ копиран(а/о)", "description": "Value has been copied to the clipboard.", @@ -2857,6 +2860,9 @@ "reviewAtRiskLoginSlideImgAltPeriod": { "message": "Илустрација листе пријаве које су ризичне." }, + "welcomeDialogGraphicAlt": { + "message": "Illustration of the layout of the Bitwarden vault page." + }, "generatePasswordSlideDesc": { "message": "Брзо генеришите снажну, јединствену лозинку са Bitwarden менијем аутопуњења за коришћење на ризичном сајту.", "description": "Description of the generate password slide on the at-risk password page carousel" @@ -3080,6 +3086,45 @@ } } }, + "durationTimeHours": { + "message": "$HOURS$ hours", + "placeholders": { + "hours": { + "content": "$1", + "example": "5" + } + } + }, + "sendCreatedDescriptionV2": { + "message": "Copy and share this Send link. The Send will be available to anyone with the link for the next $TIME$.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated.", + "placeholders": { + "time": { + "content": "$1", + "example": "7 days, 1 hour, 1 day" + } + } + }, + "sendCreatedDescriptionPassword": { + "message": "Copy and share this Send link. The Send will be available to anyone with the link and password you set for the next $TIME$.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated.", + "placeholders": { + "time": { + "content": "$1", + "example": "7 days, 1 hour, 1 day" + } + } + }, + "sendCreatedDescriptionEmail": { + "message": "Copy and share this Send link. It can be viewed by the people you specified for the next $TIME$.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated.", + "placeholders": { + "time": { + "content": "$1", + "example": "7 days, 1 hour, 1 day" + } + } + }, "sendLinkCopied": { "message": "Send линк је копиран", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." @@ -6121,6 +6166,9 @@ "enterMultipleEmailsSeparatedByComma": { "message": "Enter multiple emails by separating with a comma." }, + "emailsRequiredChangeAccessType": { + "message": "Email verification requires at least one email address. To remove all emails, change the access type above." + }, "emailPlaceholder": { "message": "user@bitwarden.com , user@acme.com" }, @@ -6133,5 +6181,8 @@ "sendPasswordHelperText": { "message": "Individuals will need to enter the password to view this Send", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "userVerificationFailed": { + "message": "User verification failed." } } diff --git a/apps/browser/src/_locales/sv/messages.json b/apps/browser/src/_locales/sv/messages.json index 484817b0210..eb5a7fef645 100644 --- a/apps/browser/src/_locales/sv/messages.json +++ b/apps/browser/src/_locales/sv/messages.json @@ -896,6 +896,9 @@ "invalidVerificationCode": { "message": "Ogiltig verifieringskod" }, + "invalidEmailOrVerificationCode": { + "message": "Invalid email or verification code" + }, "valueCopied": { "message": "$VALUE$ har kopierats", "description": "Value has been copied to the clipboard.", @@ -2857,6 +2860,9 @@ "reviewAtRiskLoginSlideImgAltPeriod": { "message": "Illustration av en lista över inloggningar som är i riskzonen." }, + "welcomeDialogGraphicAlt": { + "message": "Illustration of the layout of the Bitwarden vault page." + }, "generatePasswordSlideDesc": { "message": "Skapa snabbt ett starkt, unikt lösenord med Bitwardens autofyllmeny på riskwebbplatsen.", "description": "Description of the generate password slide on the at-risk password page carousel" @@ -3080,6 +3086,45 @@ } } }, + "durationTimeHours": { + "message": "$HOURS$ timmar", + "placeholders": { + "hours": { + "content": "$1", + "example": "5" + } + } + }, + "sendCreatedDescriptionV2": { + "message": "Kopiera och dela denna Send-länk. Denna Send kommer att vara tillgänglig för alla med länken för nästa $TIME$.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated.", + "placeholders": { + "time": { + "content": "$1", + "example": "7 days, 1 hour, 1 day" + } + } + }, + "sendCreatedDescriptionPassword": { + "message": "Kopiera och dela denna Send-länk. Denna Send kommer att vara tillgänglig för alla med den länk och lösenord du angav för nästa $TIME$.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated.", + "placeholders": { + "time": { + "content": "$1", + "example": "7 days, 1 hour, 1 day" + } + } + }, + "sendCreatedDescriptionEmail": { + "message": "Kopiera och dela denna Send-länk. Den kan visas av personer som du har angivet nästa $TIME$.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated.", + "placeholders": { + "time": { + "content": "$1", + "example": "7 days, 1 hour, 1 day" + } + } + }, "sendLinkCopied": { "message": "Skicka länk kopierad", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." @@ -6121,6 +6166,9 @@ "enterMultipleEmailsSeparatedByComma": { "message": "Ange flera e-postadresser genom att separera dem med kommatecken." }, + "emailsRequiredChangeAccessType": { + "message": "Email verification requires at least one email address. To remove all emails, change the access type above." + }, "emailPlaceholder": { "message": "användare@bitwarden.com , användare@acme.com" }, @@ -6133,5 +6181,8 @@ "sendPasswordHelperText": { "message": "Individer måste ange lösenordet för att visa denna Send", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "userVerificationFailed": { + "message": "User verification failed." } } diff --git a/apps/browser/src/_locales/ta/messages.json b/apps/browser/src/_locales/ta/messages.json index 3e76c0ab0d1..72ad809e723 100644 --- a/apps/browser/src/_locales/ta/messages.json +++ b/apps/browser/src/_locales/ta/messages.json @@ -896,6 +896,9 @@ "invalidVerificationCode": { "message": "தவறான சரிபார்ப்புக் குறியீடு" }, + "invalidEmailOrVerificationCode": { + "message": "Invalid email or verification code" + }, "valueCopied": { "message": "$VALUE$ நகலெடுக்கப்பட்டது", "description": "Value has been copied to the clipboard.", @@ -2857,6 +2860,9 @@ "reviewAtRiskLoginSlideImgAltPeriod": { "message": "ஆபத்தில் உள்ள உள்நுழைவுகளின் பட்டியலின் விளக்கம்." }, + "welcomeDialogGraphicAlt": { + "message": "Illustration of the layout of the Bitwarden vault page." + }, "generatePasswordSlideDesc": { "message": "ஆபத்தில் உள்ள தளத்தில் உள்ள Bitwarden தானாக நிரப்பு மெனுவுடன் ஒரு வலுவான, தனிப்பட்ட கடவுச்சொல்லை விரைவாக உருவாக்குங்கள்.", "description": "Description of the generate password slide on the at-risk password page carousel" @@ -3080,6 +3086,45 @@ } } }, + "durationTimeHours": { + "message": "$HOURS$ hours", + "placeholders": { + "hours": { + "content": "$1", + "example": "5" + } + } + }, + "sendCreatedDescriptionV2": { + "message": "Copy and share this Send link. The Send will be available to anyone with the link for the next $TIME$.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated.", + "placeholders": { + "time": { + "content": "$1", + "example": "7 days, 1 hour, 1 day" + } + } + }, + "sendCreatedDescriptionPassword": { + "message": "Copy and share this Send link. The Send will be available to anyone with the link and password you set for the next $TIME$.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated.", + "placeholders": { + "time": { + "content": "$1", + "example": "7 days, 1 hour, 1 day" + } + } + }, + "sendCreatedDescriptionEmail": { + "message": "Copy and share this Send link. It can be viewed by the people you specified for the next $TIME$.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated.", + "placeholders": { + "time": { + "content": "$1", + "example": "7 days, 1 hour, 1 day" + } + } + }, "sendLinkCopied": { "message": "அனுப்பு இணைப்பு நகலெடுக்கப்பட்டது", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." @@ -6121,6 +6166,9 @@ "enterMultipleEmailsSeparatedByComma": { "message": "Enter multiple emails by separating with a comma." }, + "emailsRequiredChangeAccessType": { + "message": "Email verification requires at least one email address. To remove all emails, change the access type above." + }, "emailPlaceholder": { "message": "user@bitwarden.com , user@acme.com" }, @@ -6133,5 +6181,8 @@ "sendPasswordHelperText": { "message": "Individuals will need to enter the password to view this Send", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "userVerificationFailed": { + "message": "User verification failed." } } diff --git a/apps/browser/src/_locales/te/messages.json b/apps/browser/src/_locales/te/messages.json index c28007c3838..51ca51960d7 100644 --- a/apps/browser/src/_locales/te/messages.json +++ b/apps/browser/src/_locales/te/messages.json @@ -896,6 +896,9 @@ "invalidVerificationCode": { "message": "Invalid verification code" }, + "invalidEmailOrVerificationCode": { + "message": "Invalid email or verification code" + }, "valueCopied": { "message": "$VALUE$ copied", "description": "Value has been copied to the clipboard.", @@ -2857,6 +2860,9 @@ "reviewAtRiskLoginSlideImgAltPeriod": { "message": "Illustration of a list of logins that are at-risk." }, + "welcomeDialogGraphicAlt": { + "message": "Illustration of the layout of the Bitwarden vault page." + }, "generatePasswordSlideDesc": { "message": "Quickly generate a strong, unique password with the Bitwarden autofill menu on the at-risk site.", "description": "Description of the generate password slide on the at-risk password page carousel" @@ -3080,6 +3086,45 @@ } } }, + "durationTimeHours": { + "message": "$HOURS$ hours", + "placeholders": { + "hours": { + "content": "$1", + "example": "5" + } + } + }, + "sendCreatedDescriptionV2": { + "message": "Copy and share this Send link. The Send will be available to anyone with the link for the next $TIME$.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated.", + "placeholders": { + "time": { + "content": "$1", + "example": "7 days, 1 hour, 1 day" + } + } + }, + "sendCreatedDescriptionPassword": { + "message": "Copy and share this Send link. The Send will be available to anyone with the link and password you set for the next $TIME$.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated.", + "placeholders": { + "time": { + "content": "$1", + "example": "7 days, 1 hour, 1 day" + } + } + }, + "sendCreatedDescriptionEmail": { + "message": "Copy and share this Send link. It can be viewed by the people you specified for the next $TIME$.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated.", + "placeholders": { + "time": { + "content": "$1", + "example": "7 days, 1 hour, 1 day" + } + } + }, "sendLinkCopied": { "message": "Send link copied", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." @@ -6121,6 +6166,9 @@ "enterMultipleEmailsSeparatedByComma": { "message": "Enter multiple emails by separating with a comma." }, + "emailsRequiredChangeAccessType": { + "message": "Email verification requires at least one email address. To remove all emails, change the access type above." + }, "emailPlaceholder": { "message": "user@bitwarden.com , user@acme.com" }, @@ -6133,5 +6181,8 @@ "sendPasswordHelperText": { "message": "Individuals will need to enter the password to view this Send", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "userVerificationFailed": { + "message": "User verification failed." } } diff --git a/apps/browser/src/_locales/th/messages.json b/apps/browser/src/_locales/th/messages.json index 5ec728189a8..82878eb3b52 100644 --- a/apps/browser/src/_locales/th/messages.json +++ b/apps/browser/src/_locales/th/messages.json @@ -896,6 +896,9 @@ "invalidVerificationCode": { "message": "รหัสยืนยันไม่ถูกต้อง" }, + "invalidEmailOrVerificationCode": { + "message": "Invalid email or verification code" + }, "valueCopied": { "message": "คัดลอก $VALUE$ แล้ว", "description": "Value has been copied to the clipboard.", @@ -2857,6 +2860,9 @@ "reviewAtRiskLoginSlideImgAltPeriod": { "message": "ภาพประกอบรายการข้อมูลเข้าสู่ระบบที่มีความเสี่ยง" }, + "welcomeDialogGraphicAlt": { + "message": "Illustration of the layout of the Bitwarden vault page." + }, "generatePasswordSlideDesc": { "message": "สร้างรหัสผ่านที่รัดกุมและไม่ซ้ำกันอย่างรวดเร็วด้วยเมนูป้อนอัตโนมัติของ Bitwarden บนเว็บไซต์ที่มีความเสี่ยง", "description": "Description of the generate password slide on the at-risk password page carousel" @@ -3080,6 +3086,45 @@ } } }, + "durationTimeHours": { + "message": "$HOURS$ hours", + "placeholders": { + "hours": { + "content": "$1", + "example": "5" + } + } + }, + "sendCreatedDescriptionV2": { + "message": "Copy and share this Send link. The Send will be available to anyone with the link for the next $TIME$.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated.", + "placeholders": { + "time": { + "content": "$1", + "example": "7 days, 1 hour, 1 day" + } + } + }, + "sendCreatedDescriptionPassword": { + "message": "Copy and share this Send link. The Send will be available to anyone with the link and password you set for the next $TIME$.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated.", + "placeholders": { + "time": { + "content": "$1", + "example": "7 days, 1 hour, 1 day" + } + } + }, + "sendCreatedDescriptionEmail": { + "message": "Copy and share this Send link. It can be viewed by the people you specified for the next $TIME$.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated.", + "placeholders": { + "time": { + "content": "$1", + "example": "7 days, 1 hour, 1 day" + } + } + }, "sendLinkCopied": { "message": "คัดลอกลิงก์ Send แล้ว", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." @@ -6121,6 +6166,9 @@ "enterMultipleEmailsSeparatedByComma": { "message": "Enter multiple emails by separating with a comma." }, + "emailsRequiredChangeAccessType": { + "message": "Email verification requires at least one email address. To remove all emails, change the access type above." + }, "emailPlaceholder": { "message": "user@bitwarden.com , user@acme.com" }, @@ -6133,5 +6181,8 @@ "sendPasswordHelperText": { "message": "Individuals will need to enter the password to view this Send", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "userVerificationFailed": { + "message": "User verification failed." } } diff --git a/apps/browser/src/_locales/tr/messages.json b/apps/browser/src/_locales/tr/messages.json index 7d5b31a9aba..b3d1d46c9a5 100644 --- a/apps/browser/src/_locales/tr/messages.json +++ b/apps/browser/src/_locales/tr/messages.json @@ -896,6 +896,9 @@ "invalidVerificationCode": { "message": "Geçersiz doğrulama kodu" }, + "invalidEmailOrVerificationCode": { + "message": "E-posta veya doğrulama kodu geçersiz" + }, "valueCopied": { "message": "$VALUE$ kopyalandı", "description": "Value has been copied to the clipboard.", @@ -2857,6 +2860,9 @@ "reviewAtRiskLoginSlideImgAltPeriod": { "message": "Risk altındaki hesap listesinin illüstrasyonu." }, + "welcomeDialogGraphicAlt": { + "message": "Bitwarden kasa sayfası düzeninin illüstrasyonu." + }, "generatePasswordSlideDesc": { "message": "Riskli sitede Bitwarden otomatik doldurma menüsünü kullanarak hızlıca güçlü ve benzersiz bir parola oluştur.", "description": "Description of the generate password slide on the at-risk password page carousel" @@ -3080,6 +3086,45 @@ } } }, + "durationTimeHours": { + "message": "$HOURS$ saat", + "placeholders": { + "hours": { + "content": "$1", + "example": "5" + } + } + }, + "sendCreatedDescriptionV2": { + "message": "Bu Send bağlantısını kopyalayıp paylaşın. Bu Send'e önümüzdeki $TIME$ boyunca bağlantıya sahip herkes ulaşabilecektir.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated.", + "placeholders": { + "time": { + "content": "$1", + "example": "7 days, 1 hour, 1 day" + } + } + }, + "sendCreatedDescriptionPassword": { + "message": "Bu Send bağlantısını kopyalayıp paylaşın. Bu Send'e önümüzdeki $TIME$ boyunca bağlantıya ve parolaya sahip herkes ulaşabilecektir.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated.", + "placeholders": { + "time": { + "content": "$1", + "example": "7 days, 1 hour, 1 day" + } + } + }, + "sendCreatedDescriptionEmail": { + "message": "Bu Send bağlantısını kopyalayıp paylaşın. Belirlediğiniz kişiler bağlantıyı önümüzdeki $TIME$ boyunca kullanabilir.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated.", + "placeholders": { + "time": { + "content": "$1", + "example": "7 days, 1 hour, 1 day" + } + } + }, "sendLinkCopied": { "message": "Send bağlantısı kopyalandı", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." @@ -6121,6 +6166,9 @@ "enterMultipleEmailsSeparatedByComma": { "message": "E-posta adreslerini virgülle ayırarak yazın." }, + "emailsRequiredChangeAccessType": { + "message": "E-posta doğrulaması için en az bir e-posta adresi gerekir. Tüm e-postaları silmek için yukarıdan erişim türünü değiştirin." + }, "emailPlaceholder": { "message": "kullanici@bitwarden.com , kullanici@acme.com" }, @@ -6133,5 +6181,8 @@ "sendPasswordHelperText": { "message": "Bu Send'i görmek isteyen kişilerin parola girmesi gerekecektir", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "userVerificationFailed": { + "message": "Kullanıcı doğrulaması başarısız oldu." } } diff --git a/apps/browser/src/_locales/uk/messages.json b/apps/browser/src/_locales/uk/messages.json index 49a0c9de25b..143dc8037fd 100644 --- a/apps/browser/src/_locales/uk/messages.json +++ b/apps/browser/src/_locales/uk/messages.json @@ -896,6 +896,9 @@ "invalidVerificationCode": { "message": "Недійсний код підтвердження" }, + "invalidEmailOrVerificationCode": { + "message": "Недійсна е-пошта або код підтвердження" + }, "valueCopied": { "message": "$VALUE$ скопійовано", "description": "Value has been copied to the clipboard.", @@ -1141,7 +1144,7 @@ "message": "Натисніть на запис у режимі перегляду сховища для автозаповнення" }, "clickToAutofill": { - "message": "Натисніть запис у пропозиціях для автозаповнення" + "message": "Натиснути запис у пропозиціях для автозаповнення" }, "clearClipboard": { "message": "Очистити буфер обміну", @@ -2052,7 +2055,7 @@ "message": "Е-пошта" }, "emails": { - "message": "Е-пошти" + "message": "Адреси е-пошти" }, "phone": { "message": "Телефон" @@ -2857,6 +2860,9 @@ "reviewAtRiskLoginSlideImgAltPeriod": { "message": "Ілюстрація списку ризикованих записів." }, + "welcomeDialogGraphicAlt": { + "message": "Ілюстрація макету сторінки сховища Bitwarden." + }, "generatePasswordSlideDesc": { "message": "Швидко згенеруйте надійний, унікальний пароль через меню автозаповнення Bitwarden на сайті з ризикованим паролем.", "description": "Description of the generate password slide on the at-risk password page carousel" @@ -3080,6 +3086,45 @@ } } }, + "durationTimeHours": { + "message": "$HOURS$ годин", + "placeholders": { + "hours": { + "content": "$1", + "example": "5" + } + } + }, + "sendCreatedDescriptionV2": { + "message": "Скопіюйте посилання на це відправлення і поділіться ним. Відправлення буде доступне за посиланням усім протягом $TIME$.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated.", + "placeholders": { + "time": { + "content": "$1", + "example": "7 days, 1 hour, 1 day" + } + } + }, + "sendCreatedDescriptionPassword": { + "message": "Скопіюйте посилання на це відправлення і поділіться ним. Відправлення буде доступне за посиланням і встановленим вами паролем усім протягом $TIME$.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated.", + "placeholders": { + "time": { + "content": "$1", + "example": "7 days, 1 hour, 1 day" + } + } + }, + "sendCreatedDescriptionEmail": { + "message": "Скопіюйте посилання на це відправлення і поділіться ним. Його зможуть переглядати зазначені вами користувачі протягом $TIME$.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated.", + "placeholders": { + "time": { + "content": "$1", + "example": "7 days, 1 hour, 1 day" + } + } + }, "sendLinkCopied": { "message": "Посилання на відправлення скопійовано", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." @@ -3353,7 +3398,7 @@ "message": "Не вдалося розблокувати за допомогою ключа доступу. Повторіть спробу або скористайтеся іншим способом розблокування." }, "noPrfCredentialsAvailable": { - "message": "Немає ключів доступу з підтримкою PRF, доступних для розблокування. Спочатку увійдіть з ключем доступу." + "message": "Немає ключів доступу з підтримкою PRF, доступних для розблокування. Спочатку ввійдіть з ключем доступу." }, "decryptionError": { "message": "Помилка розшифрування" @@ -4689,7 +4734,7 @@ "message": "Запропоновані записи" }, "autofillSuggestionsTip": { - "message": "Зберегти дані входу цього сайту для автозаповнення" + "message": "Збережіть дані входу цього сайту для автозаповнення" }, "yourVaultIsEmpty": { "message": "Ваше сховище порожнє" @@ -4731,7 +4776,7 @@ } }, "moreOptionsLabelNoPlaceholder": { - "message": "Більше опцій" + "message": "Інші варіанти" }, "moreOptionsTitle": { "message": "Інші можливості – $ITEMNAME$", @@ -5665,7 +5710,7 @@ "message": "Дуже широке" }, "narrow": { - "message": "Вузький" + "message": "Вузьке" }, "sshKeyWrongPassword": { "message": "Ви ввели неправильний пароль." @@ -6113,14 +6158,17 @@ "message": "Хто може переглядати" }, "specificPeople": { - "message": "Певні люди" + "message": "Певні користувачі" }, "emailVerificationDesc": { - "message": "Після того, як ви поділитеся цим посиланням на відправлення, особам необхідно буде підтвердити свої е-пошти за допомогою коду, щоб переглянути це відправлення." + "message": "Після того, як ви поділитеся посиланням на це відправлення, користувачі мають підтвердити свою е-пошту за допомогою коду, щоб переглянути його." }, "enterMultipleEmailsSeparatedByComma": { "message": "Введіть декілька адрес е-пошти, розділяючи їх комою." }, + "emailsRequiredChangeAccessType": { + "message": "Для підтвердження адреси електронної пошти потрібна щонайменше одна адреса. Щоб вилучити всі адреси електронної пошти, змініть тип доступу вище." + }, "emailPlaceholder": { "message": "user@bitwarden.com , user@acme.com" }, @@ -6131,7 +6179,10 @@ "message": "Е-пошту захищено" }, "sendPasswordHelperText": { - "message": "Особам необхідно ввести пароль для перегляду цього відправлення", + "message": "Користувачі мають ввести пароль для перегляду цього відправлення", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "userVerificationFailed": { + "message": "Не вдалося перевірити користувача." } } diff --git a/apps/browser/src/_locales/vi/messages.json b/apps/browser/src/_locales/vi/messages.json index 4f1165835cc..ebd3a2500aa 100644 --- a/apps/browser/src/_locales/vi/messages.json +++ b/apps/browser/src/_locales/vi/messages.json @@ -896,6 +896,9 @@ "invalidVerificationCode": { "message": "Mã xác minh không đúng" }, + "invalidEmailOrVerificationCode": { + "message": "Invalid email or verification code" + }, "valueCopied": { "message": "Đã sao chép $VALUE$", "description": "Value has been copied to the clipboard.", @@ -2857,6 +2860,9 @@ "reviewAtRiskLoginSlideImgAltPeriod": { "message": "Minh họa danh sách các tài khoản đăng nhập có rủi ro." }, + "welcomeDialogGraphicAlt": { + "message": "Illustration of the layout of the Bitwarden vault page." + }, "generatePasswordSlideDesc": { "message": "Tạo nhanh một mật khẩu mạnh, duy nhất bằng menu tự động điền của Bitwarden trên trang web có nguy cơ.", "description": "Description of the generate password slide on the at-risk password page carousel" @@ -3080,6 +3086,45 @@ } } }, + "durationTimeHours": { + "message": "$HOURS$ hours", + "placeholders": { + "hours": { + "content": "$1", + "example": "5" + } + } + }, + "sendCreatedDescriptionV2": { + "message": "Copy and share this Send link. The Send will be available to anyone with the link for the next $TIME$.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated.", + "placeholders": { + "time": { + "content": "$1", + "example": "7 days, 1 hour, 1 day" + } + } + }, + "sendCreatedDescriptionPassword": { + "message": "Copy and share this Send link. The Send will be available to anyone with the link and password you set for the next $TIME$.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated.", + "placeholders": { + "time": { + "content": "$1", + "example": "7 days, 1 hour, 1 day" + } + } + }, + "sendCreatedDescriptionEmail": { + "message": "Copy and share this Send link. It can be viewed by the people you specified for the next $TIME$.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated.", + "placeholders": { + "time": { + "content": "$1", + "example": "7 days, 1 hour, 1 day" + } + } + }, "sendLinkCopied": { "message": "Đã sao chép liên kết Send", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." @@ -6121,6 +6166,9 @@ "enterMultipleEmailsSeparatedByComma": { "message": "Enter multiple emails by separating with a comma." }, + "emailsRequiredChangeAccessType": { + "message": "Email verification requires at least one email address. To remove all emails, change the access type above." + }, "emailPlaceholder": { "message": "user@bitwarden.com , user@acme.com" }, @@ -6133,5 +6181,8 @@ "sendPasswordHelperText": { "message": "Individuals will need to enter the password to view this Send", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "userVerificationFailed": { + "message": "User verification failed." } } diff --git a/apps/browser/src/_locales/zh_CN/messages.json b/apps/browser/src/_locales/zh_CN/messages.json index c9dd30ab08e..c27d1a8bb24 100644 --- a/apps/browser/src/_locales/zh_CN/messages.json +++ b/apps/browser/src/_locales/zh_CN/messages.json @@ -574,10 +574,10 @@ "message": "已归档的项目将显示在此处,并将被排除在一般搜索结果和自动填充建议之外。" }, "itemArchiveToast": { - "message": "Item archived" + "message": "项目已归档" }, "itemUnarchivedToast": { - "message": "Item unarchived" + "message": "项目已取消归档" }, "archiveItem": { "message": "归档项目" @@ -896,6 +896,9 @@ "invalidVerificationCode": { "message": "无效的验证码" }, + "invalidEmailOrVerificationCode": { + "message": "无效的电子邮箱或验证码" + }, "valueCopied": { "message": "$VALUE$ 已复制", "description": "Value has been copied to the clipboard.", @@ -2857,6 +2860,9 @@ "reviewAtRiskLoginSlideImgAltPeriod": { "message": "存在风险的登录列表示意图。" }, + "welcomeDialogGraphicAlt": { + "message": "Bitwarden 密码库页面布局示意图。" + }, "generatePasswordSlideDesc": { "message": "在存在风险的网站上,使用 Bitwarden 自动填充菜单快速生成强大且唯一的密码。", "description": "Description of the generate password slide on the at-risk password page carousel" @@ -3080,6 +3086,45 @@ } } }, + "durationTimeHours": { + "message": "$HOURS$ 小时", + "placeholders": { + "hours": { + "content": "$1", + "example": "5" + } + } + }, + "sendCreatedDescriptionV2": { + "message": "复制并分享此 Send 链接。在接下来的 $TIME$ 内,拥有此链接的任何人都可以访问此 Send。", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated.", + "placeholders": { + "time": { + "content": "$1", + "example": "7 days, 1 hour, 1 day" + } + } + }, + "sendCreatedDescriptionPassword": { + "message": "复制并分享此 Send 链接。在接下来的 $TIME$ 内,拥有此链接以及您设置的密码的任何人都可以访问此 Send。", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated.", + "placeholders": { + "time": { + "content": "$1", + "example": "7 days, 1 hour, 1 day" + } + } + }, + "sendCreatedDescriptionEmail": { + "message": "复制并分享此 Send 链接。在接下来的 $TIME$ 内,您指定的人员可查看此 Send。", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated.", + "placeholders": { + "time": { + "content": "$1", + "example": "7 days, 1 hour, 1 day" + } + } + }, "sendLinkCopied": { "message": "Send 链接已复制", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." @@ -6121,6 +6166,9 @@ "enterMultipleEmailsSeparatedByComma": { "message": "输入多个电子邮箱(使用逗号分隔)。" }, + "emailsRequiredChangeAccessType": { + "message": "电子邮箱验证要求至少有一个电子邮箱地址。要移除所有电子邮箱,请更改上面的访问类型。" + }, "emailPlaceholder": { "message": "user@bitwarden.com, user@acme.com" }, @@ -6133,5 +6181,8 @@ "sendPasswordHelperText": { "message": "个人需要输入密码才能查看此 Send", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "userVerificationFailed": { + "message": "用户验证失败。" } } diff --git a/apps/browser/src/_locales/zh_TW/messages.json b/apps/browser/src/_locales/zh_TW/messages.json index 8da1b2ad08f..d23106948ad 100644 --- a/apps/browser/src/_locales/zh_TW/messages.json +++ b/apps/browser/src/_locales/zh_TW/messages.json @@ -896,6 +896,9 @@ "invalidVerificationCode": { "message": "驗證碼無效" }, + "invalidEmailOrVerificationCode": { + "message": "Invalid email or verification code" + }, "valueCopied": { "message": "$VALUE$ 已複製", "description": "Value has been copied to the clipboard.", @@ -2857,6 +2860,9 @@ "reviewAtRiskLoginSlideImgAltPeriod": { "message": "有風險登入清單的示意圖。" }, + "welcomeDialogGraphicAlt": { + "message": "Illustration of the layout of the Bitwarden vault page." + }, "generatePasswordSlideDesc": { "message": "在有風險的網站上,透過 Bitwarden 自動填入選單快速產生強且唯一的密碼。", "description": "Description of the generate password slide on the at-risk password page carousel" @@ -3080,6 +3086,45 @@ } } }, + "durationTimeHours": { + "message": "$HOURS$ 小時", + "placeholders": { + "hours": { + "content": "$1", + "example": "5" + } + } + }, + "sendCreatedDescriptionV2": { + "message": "複製並分享此 Send 連結。任何擁有此連結的人,都可在接下來的 $TIME$ 內存取該 Send。", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated.", + "placeholders": { + "time": { + "content": "$1", + "example": "7 days, 1 hour, 1 day" + } + } + }, + "sendCreatedDescriptionPassword": { + "message": "複製並分享此 Send 連結。任何擁有此連結與您所設定密碼的人,都可在接下來的 $TIME$ 內存取該 Send。", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated.", + "placeholders": { + "time": { + "content": "$1", + "example": "7 days, 1 hour, 1 day" + } + } + }, + "sendCreatedDescriptionEmail": { + "message": "複製並分享此 Send 連結。在接下來的 $TIME$ 內,只有您指定的人可以檢視。", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated.", + "placeholders": { + "time": { + "content": "$1", + "example": "7 days, 1 hour, 1 day" + } + } + }, "sendLinkCopied": { "message": "已複製 Send 連結", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." @@ -6121,6 +6166,9 @@ "enterMultipleEmailsSeparatedByComma": { "message": "請以逗號分隔輸入多個電子郵件地址。" }, + "emailsRequiredChangeAccessType": { + "message": "Email verification requires at least one email address. To remove all emails, change the access type above." + }, "emailPlaceholder": { "message": "user@bitwarden.com , user@acme.com" }, @@ -6133,5 +6181,8 @@ "sendPasswordHelperText": { "message": "對方必須輸入密碼才能檢視此 Send", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "userVerificationFailed": { + "message": "User verification failed." } } diff --git a/apps/browser/src/auth/popup/account-switching/account-switcher.component.html b/apps/browser/src/auth/popup/account-switching/account-switcher.component.html index 0a9e2a1dd9d..2eeaaf35f53 100644 --- a/apps/browser/src/auth/popup/account-switching/account-switcher.component.html +++ b/apps/browser/src/auth/popup/account-switching/account-switcher.component.html @@ -58,19 +58,19 @@ [disabled]="currentAccount.status === lockedStatus || !activeUserCanLock" [title]="!activeUserCanLock ? ('unlockMethodNeeded' | i18n) : ''" > - + {{ "lockNow" | i18n }} diff --git a/apps/browser/src/auth/popup/account-switching/account-switcher.component.ts b/apps/browser/src/auth/popup/account-switching/account-switcher.component.ts index ae7f66a9018..53d488192ba 100644 --- a/apps/browser/src/auth/popup/account-switching/account-switcher.component.ts +++ b/apps/browser/src/auth/popup/account-switching/account-switcher.component.ts @@ -18,6 +18,7 @@ import { AvatarModule, ButtonModule, DialogService, + IconModule, ItemModule, SectionComponent, SectionHeaderComponent, @@ -42,6 +43,7 @@ import { AccountSwitcherService } from "./services/account-switcher.service"; ButtonModule, ItemModule, AvatarModule, + IconModule, PopupPageComponent, PopupHeaderComponent, PopOutComponent, diff --git a/apps/browser/src/auth/popup/account-switching/account.component.html b/apps/browser/src/auth/popup/account-switching/account.component.html index 90770bb8d9b..d756365cc5b 100644 --- a/apps/browser/src/auth/popup/account-switching/account.component.html +++ b/apps/browser/src/auth/popup/account-switching/account.component.html @@ -32,13 +32,13 @@ - + diff --git a/apps/browser/src/auth/popup/account-switching/account.component.ts b/apps/browser/src/auth/popup/account-switching/account.component.ts index edfad2a54b3..dbc31a1f011 100644 --- a/apps/browser/src/auth/popup/account-switching/account.component.ts +++ b/apps/browser/src/auth/popup/account-switching/account.component.ts @@ -8,7 +8,7 @@ import { JslibModule } from "@bitwarden/angular/jslib.module"; import { AuthenticationStatus } from "@bitwarden/common/auth/enums/authentication-status"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; -import { AvatarModule, ItemModule } from "@bitwarden/components"; +import { AvatarModule, IconModule, ItemModule, type BitwardenIcon } from "@bitwarden/components"; import { BiometricsService } from "@bitwarden/key-management"; import { AccountSwitcherService, AvailableAccount } from "./services/account-switcher.service"; @@ -18,7 +18,7 @@ import { AccountSwitcherService, AvailableAccount } from "./services/account-swi @Component({ selector: "auth-account", templateUrl: "account.component.html", - imports: [CommonModule, JslibModule, AvatarModule, ItemModule], + imports: [CommonModule, JslibModule, AvatarModule, IconModule, ItemModule], }) export class AccountComponent { // FIXME(https://bitwarden.atlassian.net/browse/CL-903): Migrate to Signals @@ -60,7 +60,7 @@ export class AccountComponent { this.loading.emit(false); } - get status() { + get status(): { text: string; icon: BitwardenIcon } { if (this.account.isActive) { return { text: this.i18nService.t("active"), icon: "bwi-check-circle" }; } diff --git a/apps/browser/src/auth/popup/settings/account-security.component.html b/apps/browser/src/auth/popup/settings/account-security.component.html index bb6b141c6c5..366f5b82790 100644 --- a/apps/browser/src/auth/popup/settings/account-security.component.html +++ b/apps/browser/src/auth/popup/settings/account-security.component.html @@ -123,7 +123,7 @@ @@ -154,13 +154,13 @@ diff --git a/apps/browser/src/auth/popup/settings/account-security.component.ts b/apps/browser/src/auth/popup/settings/account-security.component.ts index 1789feebe4e..b3bd9b842f7 100644 --- a/apps/browser/src/auth/popup/settings/account-security.component.ts +++ b/apps/browser/src/auth/popup/settings/account-security.component.ts @@ -56,6 +56,7 @@ import { DialogService, FormFieldModule, IconButtonModule, + IconModule, ItemModule, LinkModule, SectionComponent, @@ -98,6 +99,7 @@ import { AwaitDesktopDialogComponent } from "./await-desktop-dialog.component"; FormsModule, ReactiveFormsModule, IconButtonModule, + IconModule, ItemModule, JslibModule, LinkModule, diff --git a/apps/browser/src/autofill/background/notification.background.spec.ts b/apps/browser/src/autofill/background/notification.background.spec.ts index 95d4111987b..1bd1ae5513b 100644 --- a/apps/browser/src/autofill/background/notification.background.spec.ts +++ b/apps/browser/src/autofill/background/notification.background.spec.ts @@ -28,6 +28,7 @@ import { TaskService, SecurityTask } from "@bitwarden/common/vault/tasks"; import { BrowserApi } from "../../platform/browser/browser-api"; import { NotificationType } from "../enums/notification-type.enum"; +import { Fido2Background } from "../fido2/background/abstractions/fido2.background"; import { FormData } from "../services/abstractions/autofill.service"; import AutofillService from "../services/autofill.service"; import { createAutofillPageDetailsMock, createChromeTabMock } from "../spec/autofill-mocks"; @@ -81,6 +82,8 @@ describe("NotificationBackground", () => { const configService = mock(); const accountService = mock(); const organizationService = mock(); + const fido2Background = mock(); + fido2Background.isCredentialRequestInProgress.mockReturnValue(false); const userId = "testId" as UserId; const activeAccountSubject = new BehaviorSubject({ @@ -115,6 +118,7 @@ describe("NotificationBackground", () => { userNotificationSettingsService, taskService, messagingService, + fido2Background, ); }); @@ -759,7 +763,6 @@ describe("NotificationBackground", () => { notificationBackground as any, "getEnableChangedPasswordPrompt", ); - pushChangePasswordToQueueSpy = jest.spyOn( notificationBackground as any, "pushChangePasswordToQueue", @@ -822,6 +825,22 @@ describe("NotificationBackground", () => { expectSkippedCheckingNotification(); }); + it("skips checking if a notification should trigger if a fido2 credential request is in progress for the tab", async () => { + const formEntryData: ModifyLoginCipherFormData = { + newPassword: "", + password: "", + uri: mockFormURI, + username: "ADent", + }; + + activeAccountStatusMock$.next(AuthenticationStatus.Unlocked); + fido2Background.isCredentialRequestInProgress.mockReturnValueOnce(true); + + await notificationBackground.triggerCipherNotification(formEntryData, tab); + + expectSkippedCheckingNotification(); + }); + it("skips checking if a notification should trigger if the user has disabled both the new login and update password notification", async () => { const formEntryData: ModifyLoginCipherFormData = { newPassword: "Bab3lPhs5h", diff --git a/apps/browser/src/autofill/background/notification.background.ts b/apps/browser/src/autofill/background/notification.background.ts index 3713cd7c4c2..64c52701e21 100644 --- a/apps/browser/src/autofill/background/notification.background.ts +++ b/apps/browser/src/autofill/background/notification.background.ts @@ -61,6 +61,7 @@ import { } from "../content/components/cipher/types"; import { CollectionView } from "../content/components/common-types"; import { NotificationType } from "../enums/notification-type.enum"; +import { Fido2Background } from "../fido2/background/abstractions/fido2.background"; import { AutofillService } from "../services/abstractions/autofill.service"; import { TemporaryNotificationChangeLoginService } from "../services/notification-change-login-password.service"; @@ -165,6 +166,7 @@ export default class NotificationBackground { private userNotificationSettingsService: UserNotificationSettingsServiceAbstraction, private taskService: TaskService, protected messagingService: MessagingService, + private fido2Background: Fido2Background, ) {} init() { @@ -665,6 +667,11 @@ export default class NotificationBackground { return false; } + // If there is an active passkey prompt, exit early + if (this.fido2Background.isCredentialRequestInProgress(tab.id)) { + return false; + } + // If no cipher add/update notifications are enabled, we can exit early const changePasswordNotificationIsEnabled = await this.getEnableChangedPasswordPrompt(); const newLoginNotificationIsEnabled = await this.getEnableAddedLoginPrompt(); diff --git a/apps/browser/src/autofill/fido2/background/abstractions/fido2.background.ts b/apps/browser/src/autofill/fido2/background/abstractions/fido2.background.ts index 6ad069ad56e..c5346d61566 100644 --- a/apps/browser/src/autofill/fido2/background/abstractions/fido2.background.ts +++ b/apps/browser/src/autofill/fido2/background/abstractions/fido2.background.ts @@ -45,6 +45,8 @@ type Fido2BackgroundExtensionMessageHandlers = { interface Fido2Background { init(): void; + isCredentialRequestInProgress(tabId: number): boolean; + isPasskeySettingEnabled(): Promise; } export { diff --git a/apps/browser/src/autofill/fido2/background/fido2.background.spec.ts b/apps/browser/src/autofill/fido2/background/fido2.background.spec.ts index 752851b3d37..6ead7416b96 100644 --- a/apps/browser/src/autofill/fido2/background/fido2.background.spec.ts +++ b/apps/browser/src/autofill/fido2/background/fido2.background.spec.ts @@ -256,6 +256,84 @@ describe("Fido2Background", () => { }); }); + describe("isCredentialRequestInProgress", () => { + beforeEach(() => { + fido2Background.init(); + }); + + it("returns false when no credential request is active", () => { + expect(fido2Background.isCredentialRequestInProgress(tabMock.id)).toBe(false); + }); + + it("returns true while a register credential request is in progress", async () => { + let duringRequestResult: boolean; + fido2ClientService.createCredential.mockImplementation(async () => { + duringRequestResult = fido2Background.isCredentialRequestInProgress(tabMock.id); + return mock(); + }); + + const message = mock({ + command: "fido2RegisterCredentialRequest", + requestId: "123", + data: mock(), + }); + + sendMockExtensionMessage(message, senderMock); + await flushPromises(); + + expect(duringRequestResult).toBe(true); + }); + + it("returns true while a get credential request is in progress", async () => { + let duringRequestResult: boolean; + fido2ClientService.assertCredential.mockImplementation(async () => { + duringRequestResult = fido2Background.isCredentialRequestInProgress(tabMock.id); + return mock(); + }); + + const message = mock({ + command: "fido2GetCredentialRequest", + requestId: "123", + data: mock(), + }); + + sendMockExtensionMessage(message, senderMock); + await flushPromises(); + + expect(duringRequestResult).toBe(true); + }); + + it("returns false after a credential request completes", async () => { + fido2ClientService.createCredential.mockResolvedValue(mock()); + + const message = mock({ + command: "fido2RegisterCredentialRequest", + requestId: "123", + data: mock(), + }); + + sendMockExtensionMessage(message, senderMock); + await flushPromises(); + + expect(fido2Background.isCredentialRequestInProgress(tabMock.id)).toBe(false); + }); + + it("returns false after a credential request errors", async () => { + fido2ClientService.createCredential.mockRejectedValue(new Error("error")); + + const message = mock({ + command: "fido2RegisterCredentialRequest", + requestId: "123", + data: mock(), + }); + + sendMockExtensionMessage(message, senderMock); + await flushPromises(); + + expect(fido2Background.isCredentialRequestInProgress(tabMock.id)).toBe(false); + }); + }); + describe("extension message handlers", () => { beforeEach(() => { fido2Background.init(); diff --git a/apps/browser/src/autofill/fido2/background/fido2.background.ts b/apps/browser/src/autofill/fido2/background/fido2.background.ts index 22ee4a1822d..495b0d85f0b 100644 --- a/apps/browser/src/autofill/fido2/background/fido2.background.ts +++ b/apps/browser/src/autofill/fido2/background/fido2.background.ts @@ -35,6 +35,7 @@ export class Fido2Background implements Fido2BackgroundInterface { private currentAuthStatus$: Subscription; private abortManager = new AbortManager(); private fido2ContentScriptPortsSet = new Set(); + private activeCredentialRequests = new Set(); private registeredContentScripts: browser.contentScripts.RegisteredContentScript; private readonly sharedInjectionDetails: SharedFido2ScriptInjectionDetails = { runAt: "document_start", @@ -61,6 +62,16 @@ export class Fido2Background implements Fido2BackgroundInterface { private authService: AuthService, ) {} + /** + * Checks if a FIDO2 credential request (registration or assertion) + * is currently in progress for the given tab. + * + * @param tabId - The tab id to check. + */ + isCredentialRequestInProgress(tabId: number): boolean { + return this.activeCredentialRequests.has(tabId); + } + /** * Initializes the FIDO2 background service. Sets up the extension message * and port listeners. Subscribes to the enablePasskeys$ observable to @@ -307,20 +318,25 @@ export class Fido2Background implements Fido2BackgroundInterface { abortController: AbortController, ) => Promise, ) => { - return await this.abortManager.runWithAbortController(requestId, async (abortController) => { - try { - return await callback(data, tab, abortController); - } finally { - await BrowserApi.focusTab(tab.id); - await BrowserApi.focusWindow(tab.windowId); - } - }); + this.activeCredentialRequests.add(tab.id); + try { + return await this.abortManager.runWithAbortController(requestId, async (abortController) => { + try { + return await callback(data, tab, abortController); + } finally { + await BrowserApi.focusTab(tab.id); + await BrowserApi.focusWindow(tab.windowId); + } + }); + } finally { + this.activeCredentialRequests.delete(tab.id); + } }; /** * Checks if the enablePasskeys setting is enabled. */ - private async isPasskeySettingEnabled() { + async isPasskeySettingEnabled() { return await firstValueFrom(this.vaultSettingsService.enablePasskeys$); } diff --git a/apps/browser/src/autofill/fido2/services/browser-fido2-user-interface.service.ts b/apps/browser/src/autofill/fido2/services/browser-fido2-user-interface.service.ts index 19c1dbc8790..11dc170db16 100644 --- a/apps/browser/src/autofill/fido2/services/browser-fido2-user-interface.service.ts +++ b/apps/browser/src/autofill/fido2/services/browser-fido2-user-interface.service.ts @@ -363,6 +363,9 @@ export class BrowserFido2UserInterfaceSession implements Fido2UserInterfaceSessi ), ); + // Defensive measure in case an existing notification appeared before the passkey popout + await BrowserApi.tabSendMessageData(this.tab, "closeNotificationBar"); + const popoutId = await openFido2Popout(this.tab, { sessionId: this.sessionId, fallbackSupported: this.fallbackSupported, diff --git a/apps/browser/src/background/main.background.ts b/apps/browser/src/background/main.background.ts index 25c7b344982..fc83cdb136c 100644 --- a/apps/browser/src/background/main.background.ts +++ b/apps/browser/src/background/main.background.ts @@ -848,7 +848,7 @@ export default class MainBackground { this.accountService, this.kdfConfigService, this.keyService, - this.securityStateService, + this.accountCryptographicStateService, this.apiService, this.stateProvider, this.configService, @@ -1409,6 +1409,7 @@ export default class MainBackground { this.userNotificationSettingsService, this.taskService, this.messagingService, + this.fido2Background, ); this.overlayNotificationsBackground = new OverlayNotificationsBackground( diff --git a/apps/browser/src/manifest.json b/apps/browser/src/manifest.json index c2e0b422985..54a10e34b12 100644 --- a/apps/browser/src/manifest.json +++ b/apps/browser/src/manifest.json @@ -2,7 +2,7 @@ "manifest_version": 2, "name": "__MSG_extName__", "short_name": "Bitwarden", - "version": "2026.1.1", + "version": "2026.2.0", "description": "__MSG_extDesc__", "default_locale": "en", "author": "Bitwarden Inc.", diff --git a/apps/browser/src/manifest.v3.json b/apps/browser/src/manifest.v3.json index 603d3e06ba7..206a300236c 100644 --- a/apps/browser/src/manifest.v3.json +++ b/apps/browser/src/manifest.v3.json @@ -3,7 +3,7 @@ "minimum_chrome_version": "102.0", "name": "__MSG_extName__", "short_name": "Bitwarden", - "version": "2026.1.1", + "version": "2026.2.0", "description": "__MSG_extDesc__", "default_locale": "en", "author": "Bitwarden Inc.", diff --git a/apps/browser/src/platform/browser/browser-api.spec.ts b/apps/browser/src/platform/browser/browser-api.spec.ts index f7561b2b50b..d8a8fe52570 100644 --- a/apps/browser/src/platform/browser/browser-api.spec.ts +++ b/apps/browser/src/platform/browser/browser-api.spec.ts @@ -1,5 +1,7 @@ import { mock } from "jest-mock-extended"; +import { LogService } from "@bitwarden/logging"; + import { BrowserApi } from "./browser-api"; type ChromeSettingsGet = chrome.types.ChromeSetting["get"]; @@ -29,6 +31,104 @@ describe("BrowserApi", () => { }); }); + describe("senderIsInternal", () => { + const EXTENSION_ORIGIN = "chrome-extension://id"; + + beforeEach(() => { + jest.spyOn(BrowserApi, "getRuntimeURL").mockReturnValue(`${EXTENSION_ORIGIN}/`); + }); + + it("returns false when sender is undefined", () => { + const result = BrowserApi.senderIsInternal(undefined); + + expect(result).toBe(false); + }); + + it("returns false when sender has no origin", () => { + const result = BrowserApi.senderIsInternal({ id: "abc" } as any); + + expect(result).toBe(false); + }); + + it("returns false when the extension URL cannot be determined", () => { + jest.spyOn(BrowserApi, "getRuntimeURL").mockReturnValue(""); + + const result = BrowserApi.senderIsInternal({ origin: EXTENSION_ORIGIN }); + + expect(result).toBe(false); + }); + + it.each([ + ["an external origin", "https://evil.com"], + ["a subdomain of the extension origin", "chrome-extension://id.evil.com"], + ["a file: URL (opaque origin)", "file:///home/user/page.html"], + ["a data: URL (opaque origin)", "data:text/html,

hi

"], + ])("returns false when sender origin is %s", (_, senderOrigin) => { + const result = BrowserApi.senderIsInternal({ origin: senderOrigin }); + + expect(result).toBe(false); + }); + + it("returns false when sender is from a non-top-level frame", () => { + const result = BrowserApi.senderIsInternal({ origin: EXTENSION_ORIGIN, frameId: 5 }); + + expect(result).toBe(false); + }); + + it("returns true when sender origin matches and no frameId is present (popup)", () => { + const result = BrowserApi.senderIsInternal({ origin: EXTENSION_ORIGIN }); + + expect(result).toBe(true); + }); + + it("returns true when sender origin matches and frameId is 0 (top-level frame)", () => { + const result = BrowserApi.senderIsInternal({ origin: EXTENSION_ORIGIN, frameId: 0 }); + + expect(result).toBe(true); + }); + + it("calls logger.warning when sender has no origin", () => { + const logger = mock(); + + BrowserApi.senderIsInternal({} as any, logger); + + expect(logger.warning).toHaveBeenCalledWith(expect.stringContaining("no origin")); + }); + + it("calls logger.warning when the extension URL cannot be determined", () => { + jest.spyOn(BrowserApi, "getRuntimeURL").mockReturnValue(""); + const logger = mock(); + + BrowserApi.senderIsInternal({ origin: EXTENSION_ORIGIN }, logger); + + expect(logger.warning).toHaveBeenCalledWith(expect.stringContaining("extension URL")); + }); + + it("calls logger.warning when origin does not match", () => { + const logger = mock(); + + BrowserApi.senderIsInternal({ origin: "https://evil.com" }, logger); + + expect(logger.warning).toHaveBeenCalledWith(expect.stringContaining("does not match")); + }); + + it("calls logger.warning when sender is from a non-top-level frame", () => { + const logger = mock(); + + BrowserApi.senderIsInternal({ origin: EXTENSION_ORIGIN, frameId: 5 }, logger); + + expect(logger.warning).toHaveBeenCalledWith(expect.stringContaining("top-level frame")); + }); + + it("calls logger.info when sender is confirmed internal", () => { + const logger = mock(); + + BrowserApi.senderIsInternal({ origin: EXTENSION_ORIGIN }, logger); + + expect(logger.info).toHaveBeenCalledWith(expect.stringContaining("internal")); + }); + }); + describe("getWindow", () => { it("will get the current window if a window id is not provided", () => { // FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling. diff --git a/apps/browser/src/platform/browser/browser-api.ts b/apps/browser/src/platform/browser/browser-api.ts index feefd527636..1b0f7639d1d 100644 --- a/apps/browser/src/platform/browser/browser-api.ts +++ b/apps/browser/src/platform/browser/browser-api.ts @@ -6,7 +6,7 @@ import { BrowserClientVendors } from "@bitwarden/common/autofill/constants"; import { BrowserClientVendor } from "@bitwarden/common/autofill/types"; import { DeviceType } from "@bitwarden/common/enums"; import { LogService } from "@bitwarden/logging"; -import { isBrowserSafariApi } from "@bitwarden/platform"; +import { isBrowserSafariApi, urlOriginsMatch } from "@bitwarden/platform"; import { TabMessage } from "../../types/tab-messages"; import { BrowserPlatformUtilsService } from "../services/platform-utils/browser-platform-utils.service"; @@ -34,12 +34,20 @@ export class BrowserApi { } /** - * Helper method that attempts to distinguish whether a message sender is internal to the extension or not. + * Returns `true` if the message sender appears to originate from within this extension. * - * Currently this is done through source origin matching, and frameId checking (only top-level frames are internal). - * @param sender a message sender - * @param logger an optional logger to log validation results - * @returns whether or not the sender appears to be internal to the extension + * Returns `false` when: + * - `sender` is absent or has no `origin` property + * - The extension's own URL cannot be determined at runtime + * - The sender's origin does not match the extension's origin (compared by scheme, host, and port; + * senders without a host such as `file:` or `data:` URLs are always rejected) + * - The message comes from a sub-frame rather than the top-level frame + * + * Note: this is a best-effort check that relies on the browser correctly populating `sender.origin`. + * + * @param sender - The message sender to validate. `undefined` or a sender without `origin` returns `false`. + * @param logger - Optional logger; rejections are reported at `warning` level, acceptance at `info`. + * @returns `true` if the sender appears to be internal to the extension; `false` otherwise. */ static senderIsInternal( sender: chrome.runtime.MessageSender | undefined, @@ -49,28 +57,22 @@ export class BrowserApi { logger?.warning("[BrowserApi] Message sender has no origin"); return false; } - const extensionUrl = - (typeof chrome !== "undefined" && chrome.runtime?.getURL("")) || - (typeof browser !== "undefined" && browser.runtime?.getURL("")) || - ""; + // Empty path yields the extension's base URL; coalesce to empty string so the guard below fires on a missing runtime. + const extensionUrl = BrowserApi.getRuntimeURL("") ?? ""; if (!extensionUrl) { logger?.warning("[BrowserApi] Unable to determine extension URL"); return false; } - // Normalize both URLs by removing trailing slashes - const normalizedOrigin = sender.origin.replace(/\/$/, "").toLowerCase(); - const normalizedExtensionUrl = extensionUrl.replace(/\/$/, "").toLowerCase(); - - if (!normalizedOrigin.startsWith(normalizedExtensionUrl)) { + if (!urlOriginsMatch(extensionUrl, sender.origin)) { logger?.warning( - `[BrowserApi] Message sender origin (${normalizedOrigin}) does not match extension URL (${normalizedExtensionUrl})`, + `[BrowserApi] Message sender origin (${sender.origin}) does not match extension URL (${extensionUrl})`, ); return false; } - // We only send messages from the top-level frame, but frameId is only set if tab is set, which for popups it is not. + // frameId is absent for popups, so use an 'in' check rather than direct comparison. if ("frameId" in sender && sender.frameId !== 0) { logger?.warning("[BrowserApi] Message sender is not from the top-level frame"); return false; diff --git a/apps/browser/src/popup/app-routing.module.ts b/apps/browser/src/popup/app-routing.module.ts index 0d85743bba7..01d713742a5 100644 --- a/apps/browser/src/popup/app-routing.module.ts +++ b/apps/browser/src/popup/app-routing.module.ts @@ -356,7 +356,7 @@ const routes: Routes = [ { path: "edit-send", component: SendAddEditV2Component, - canActivate: [authGuard, filePickerPopoutGuard()], + canActivate: [authGuard], data: { elevation: 1 } satisfies RouteDataProperties, }, { diff --git a/apps/browser/src/tools/popup/guards/file-picker-popout.guard.spec.ts b/apps/browser/src/tools/popup/guards/file-picker-popout.guard.spec.ts index 2f100ab67f2..64b7ac673c1 100644 --- a/apps/browser/src/tools/popup/guards/file-picker-popout.guard.spec.ts +++ b/apps/browser/src/tools/popup/guards/file-picker-popout.guard.spec.ts @@ -269,23 +269,21 @@ describe("filePickerPopoutGuard", () => { inSidebarSpy.mockReturnValue(false); }); - it.each([ - { route: "/import" }, - { route: "/add-send" }, - { route: "/edit-send" }, - { route: "/attachments" }, - ])("should open popout for $route route", async ({ route }) => { - const importState: RouterStateSnapshot = { - url: route, - } as RouterStateSnapshot; + it.each([{ route: "/import" }, { route: "/add-send" }, { route: "/attachments" }])( + "should open popout for $route route", + async ({ route }) => { + const importState: RouterStateSnapshot = { + url: route, + } as RouterStateSnapshot; - const guard = filePickerPopoutGuard(); - const result = await TestBed.runInInjectionContext(() => guard(mockRoute, importState)); + const guard = filePickerPopoutGuard(); + const result = await TestBed.runInInjectionContext(() => guard(mockRoute, importState)); - expect(openPopoutSpy).toHaveBeenCalledWith("popup/index.html#" + route); - expect(closePopupSpy).toHaveBeenCalledWith(window); - expect(result).toBe(false); - }); + expect(openPopoutSpy).toHaveBeenCalledWith("popup/index.html#" + route); + expect(closePopupSpy).toHaveBeenCalledWith(window); + expect(result).toBe(false); + }, + ); }); describe("Url handling", () => { @@ -354,30 +352,6 @@ describe("filePickerPopoutGuard", () => { expect(result).toBe(true); }, ); - - it.each([ - { deviceType: DeviceType.FirefoxExtension, name: "Firefox" }, - { deviceType: DeviceType.SafariExtension, name: "Safari" }, - { deviceType: DeviceType.ChromeExtension, name: "Chrome" }, - { deviceType: DeviceType.EdgeExtension, name: "Edge" }, - ])("should allow navigation for editing text Sends on $name", async ({ deviceType }) => { - getDeviceSpy.mockReturnValue(deviceType); - inPopoutSpy.mockReturnValue(false); - inSidebarSpy.mockReturnValue(false); - - const editTextSendState: RouterStateSnapshot = { - url: "/edit-send?sendId=abc123&type=0", - } as RouterStateSnapshot; - - const guard = filePickerPopoutGuard(); - const result = await TestBed.runInInjectionContext(() => - guard(mockRoute, editTextSendState), - ); - - expect(openPopoutSpy).not.toHaveBeenCalled(); - expect(closePopupSpy).not.toHaveBeenCalled(); - expect(result).toBe(true); - }); }); describe("File Sends (type=1)", () => { @@ -487,115 +461,6 @@ describe("filePickerPopoutGuard", () => { } }, ); - - it.each([ - { - deviceType: DeviceType.FirefoxExtension, - name: "Firefox", - os: "Mac", - userAgent: "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7)", - expectPopout: true, - }, - { - deviceType: DeviceType.FirefoxExtension, - name: "Firefox", - os: "Linux", - userAgent: "Mozilla/5.0 (X11; Linux x86_64)", - expectPopout: true, - }, - { - deviceType: DeviceType.FirefoxExtension, - name: "Firefox", - os: "Windows", - userAgent: "Mozilla/5.0 (Windows NT 10.0; Win64; x64)", - expectPopout: true, - }, - { - deviceType: DeviceType.SafariExtension, - name: "Safari", - os: "Mac", - userAgent: "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7)", - expectPopout: true, - }, - { - deviceType: DeviceType.ChromeExtension, - name: "Chrome", - os: "Mac", - userAgent: "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7)", - expectPopout: true, - }, - { - deviceType: DeviceType.ChromeExtension, - name: "Chrome", - os: "Linux", - userAgent: "Mozilla/5.0 (X11; Linux x86_64)", - expectPopout: true, - }, - { - deviceType: DeviceType.ChromeExtension, - name: "Chrome", - os: "Windows", - userAgent: "Mozilla/5.0 (Windows NT 10.0; Win64; x64)", - expectPopout: false, - }, - { - deviceType: DeviceType.EdgeExtension, - name: "Edge", - os: "Mac", - userAgent: "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7)", - expectPopout: true, - }, - { - deviceType: DeviceType.EdgeExtension, - name: "Edge", - os: "Linux", - userAgent: "Mozilla/5.0 (X11; Linux x86_64)", - expectPopout: true, - }, - { - deviceType: DeviceType.EdgeExtension, - name: "Edge", - os: "Windows", - userAgent: "Mozilla/5.0 (Windows NT 10.0; Win64; x64)", - expectPopout: false, - }, - ])( - "should require popout for editing a file Send on $name $os", - async ({ deviceType, userAgent, expectPopout }) => { - getDeviceSpy.mockReturnValue(deviceType); - inPopoutSpy.mockReturnValue(false); - inSidebarSpy.mockReturnValue(false); - - if (userAgent) { - Object.defineProperty(window, "navigator", { - value: { userAgent, appVersion: userAgent }, - configurable: true, - writable: true, - }); - } - - const editFileSendState: RouterStateSnapshot = { - url: "/edit-send?sendId=abc123&type=1", - } as RouterStateSnapshot; - - const guard = filePickerPopoutGuard(); - const result = await TestBed.runInInjectionContext(() => - guard(mockRoute, editFileSendState), - ); - - if (expectPopout === false) { - expect(openPopoutSpy).not.toHaveBeenCalled(); - expect(closePopupSpy).not.toHaveBeenCalled(); - expect(result).toBe(true); - } else { - expect(openPopoutSpy).toHaveBeenCalledWith( - "popup/index.html#/edit-send?sendId=abc123&type=1", - ); - expect(closePopupSpy).toHaveBeenCalledWith(window); - expect(result).toBe(false); - } - }, - ); }); describe("Send routes without type parameter", () => { diff --git a/apps/browser/src/tools/popup/guards/file-picker-popout.guard.ts b/apps/browser/src/tools/popup/guards/file-picker-popout.guard.ts index 900ff328ac8..48a73ed780f 100644 --- a/apps/browser/src/tools/popup/guards/file-picker-popout.guard.ts +++ b/apps/browser/src/tools/popup/guards/file-picker-popout.guard.ts @@ -24,9 +24,9 @@ import { SendType } from "@bitwarden/common/tools/send/types/send-type"; */ export function filePickerPopoutGuard(): CanActivateFn { return async (_route: ActivatedRouteSnapshot, state: RouterStateSnapshot) => { - // Check if this is a text Send route (no file picker needed) - if (isTextOnlySendRoute(state.url)) { - return true; // Allow navigation without popout + // Text Sends have no file picker — never require a popout regardless of browser + if (isTextSendRoute(state.url)) { + return true; } // Check if browser is one that needs popout for file pickers @@ -81,29 +81,16 @@ export function filePickerPopoutGuard(): CanActivateFn { } /** - * Determines if the route is for a text Send that doesn't require file picker display. - * - * @param url The route URL with query parameters - * @returns true if this is a Send route with explicitly text type (SendType.Text = 0) + * Returns true when the add-send route targets a Text Send (type=0). + * Text Sends have no file picker and never require a popout window. */ -function isTextOnlySendRoute(url: string): boolean { - // Only apply to Send routes - if (!url.includes("/add-send") && !url.includes("/edit-send")) { +function isTextSendRoute(url: string): boolean { + if (!url.includes("/add-send")) { return false; } - - // Parse query parameters to check Send type - const queryStartIndex = url.indexOf("?"); - if (queryStartIndex === -1) { - // No query params - default to requiring popout for safety + const queryStart = url.indexOf("?"); + if (queryStart === -1) { return false; } - - const queryString = url.substring(queryStartIndex + 1); - const params = new URLSearchParams(queryString); - const typeParam = params.get("type"); - - // Only skip popout for explicitly text-based Sends (SendType.Text = 0) - // If type is missing, null, or not text, default to requiring popout - return typeParam === String(SendType.Text); + return new URLSearchParams(url.substring(queryStart + 1)).get("type") === String(SendType.Text); } diff --git a/apps/browser/src/tools/popup/guards/firefox-popout.guard.spec.ts b/apps/browser/src/tools/popup/guards/firefox-popout.guard.spec.ts deleted file mode 100644 index df04b965d4c..00000000000 --- a/apps/browser/src/tools/popup/guards/firefox-popout.guard.spec.ts +++ /dev/null @@ -1,194 +0,0 @@ -import { TestBed } from "@angular/core/testing"; -import { ActivatedRouteSnapshot, RouterStateSnapshot } from "@angular/router"; - -import { DeviceType } from "@bitwarden/common/enums"; - -import { BrowserApi } from "../../../platform/browser/browser-api"; -import BrowserPopupUtils from "../../../platform/browser/browser-popup-utils"; -import { BrowserPlatformUtilsService } from "../../../platform/services/platform-utils/browser-platform-utils.service"; - -import { firefoxPopoutGuard } from "./firefox-popout.guard"; - -describe("firefoxPopoutGuard", () => { - let getDeviceSpy: jest.SpyInstance; - let inPopoutSpy: jest.SpyInstance; - let inSidebarSpy: jest.SpyInstance; - let openPopoutSpy: jest.SpyInstance; - let closePopupSpy: jest.SpyInstance; - - const mockRoute = {} as ActivatedRouteSnapshot; - const mockState: RouterStateSnapshot = { - url: "/import?param=value", - } as RouterStateSnapshot; - - beforeEach(() => { - getDeviceSpy = jest.spyOn(BrowserPlatformUtilsService, "getDevice"); - inPopoutSpy = jest.spyOn(BrowserPopupUtils, "inPopout"); - inSidebarSpy = jest.spyOn(BrowserPopupUtils, "inSidebar"); - openPopoutSpy = jest.spyOn(BrowserPopupUtils, "openPopout").mockImplementation(); - closePopupSpy = jest.spyOn(BrowserApi, "closePopup").mockImplementation(); - - TestBed.configureTestingModule({}); - }); - - afterEach(() => { - jest.clearAllMocks(); - }); - - describe("when browser is Firefox", () => { - beforeEach(() => { - getDeviceSpy.mockReturnValue(DeviceType.FirefoxExtension); - inPopoutSpy.mockReturnValue(false); - inSidebarSpy.mockReturnValue(false); - }); - - it("should open popout and block navigation when not already in popout or sidebar", async () => { - const guard = firefoxPopoutGuard(); - const result = await TestBed.runInInjectionContext(() => guard(mockRoute, mockState)); - - expect(getDeviceSpy).toHaveBeenCalledWith(window); - expect(inPopoutSpy).toHaveBeenCalledWith(window); - expect(inSidebarSpy).toHaveBeenCalledWith(window); - expect(openPopoutSpy).toHaveBeenCalledWith("popup/index.html#/import?param=value"); - expect(closePopupSpy).toHaveBeenCalledWith(window); - expect(result).toBe(false); - }); - - it("should not add autoClosePopout parameter to the url", async () => { - const guard = firefoxPopoutGuard(); - await TestBed.runInInjectionContext(() => guard(mockRoute, mockState)); - - expect(openPopoutSpy).toHaveBeenCalledWith("popup/index.html#/import?param=value"); - expect(openPopoutSpy).not.toHaveBeenCalledWith(expect.stringContaining("autoClosePopout")); - }); - - it("should allow navigation when already in popout", async () => { - inPopoutSpy.mockReturnValue(true); - - const guard = firefoxPopoutGuard(); - const result = await TestBed.runInInjectionContext(() => guard(mockRoute, mockState)); - - expect(openPopoutSpy).not.toHaveBeenCalled(); - expect(closePopupSpy).not.toHaveBeenCalled(); - expect(result).toBe(true); - }); - - it("should allow navigation when already in sidebar", async () => { - inSidebarSpy.mockReturnValue(true); - - const guard = firefoxPopoutGuard(); - const result = await TestBed.runInInjectionContext(() => guard(mockRoute, mockState)); - - expect(openPopoutSpy).not.toHaveBeenCalled(); - expect(closePopupSpy).not.toHaveBeenCalled(); - expect(result).toBe(true); - }); - }); - - describe("when browser is not Firefox", () => { - beforeEach(() => { - inPopoutSpy.mockReturnValue(false); - inSidebarSpy.mockReturnValue(false); - }); - - it.each([ - { deviceType: DeviceType.ChromeExtension, name: "ChromeExtension" }, - { deviceType: DeviceType.EdgeExtension, name: "EdgeExtension" }, - { deviceType: DeviceType.OperaExtension, name: "OperaExtension" }, - { deviceType: DeviceType.SafariExtension, name: "SafariExtension" }, - { deviceType: DeviceType.VivaldiExtension, name: "VivaldiExtension" }, - ])( - "should allow navigation without opening popout when device is $name", - async ({ deviceType }) => { - getDeviceSpy.mockReturnValue(deviceType); - - const guard = firefoxPopoutGuard(); - const result = await TestBed.runInInjectionContext(() => guard(mockRoute, mockState)); - - expect(getDeviceSpy).toHaveBeenCalledWith(window); - expect(openPopoutSpy).not.toHaveBeenCalled(); - expect(closePopupSpy).not.toHaveBeenCalled(); - expect(result).toBe(true); - }, - ); - }); - - describe("file picker routes", () => { - beforeEach(() => { - getDeviceSpy.mockReturnValue(DeviceType.FirefoxExtension); - inPopoutSpy.mockReturnValue(false); - inSidebarSpy.mockReturnValue(false); - }); - - it("should open popout for /import route", async () => { - const importState: RouterStateSnapshot = { - url: "/import", - } as RouterStateSnapshot; - - const guard = firefoxPopoutGuard(); - const result = await TestBed.runInInjectionContext(() => guard(mockRoute, importState)); - - expect(openPopoutSpy).toHaveBeenCalledWith("popup/index.html#/import"); - expect(closePopupSpy).toHaveBeenCalledWith(window); - expect(result).toBe(false); - }); - - it("should open popout for /add-send route", async () => { - const addSendState: RouterStateSnapshot = { - url: "/add-send", - } as RouterStateSnapshot; - - const guard = firefoxPopoutGuard(); - const result = await TestBed.runInInjectionContext(() => guard(mockRoute, addSendState)); - - expect(openPopoutSpy).toHaveBeenCalledWith("popup/index.html#/add-send"); - expect(closePopupSpy).toHaveBeenCalledWith(window); - expect(result).toBe(false); - }); - - it("should open popout for /edit-send route", async () => { - const editSendState: RouterStateSnapshot = { - url: "/edit-send", - } as RouterStateSnapshot; - - const guard = firefoxPopoutGuard(); - const result = await TestBed.runInInjectionContext(() => guard(mockRoute, editSendState)); - - expect(openPopoutSpy).toHaveBeenCalledWith("popup/index.html#/edit-send"); - expect(closePopupSpy).toHaveBeenCalledWith(window); - expect(result).toBe(false); - }); - }); - - describe("url handling", () => { - beforeEach(() => { - getDeviceSpy.mockReturnValue(DeviceType.FirefoxExtension); - inPopoutSpy.mockReturnValue(false); - inSidebarSpy.mockReturnValue(false); - }); - - it("should preserve query parameters in the popout url", async () => { - const stateWithQuery: RouterStateSnapshot = { - url: "/import?foo=bar&baz=qux", - } as RouterStateSnapshot; - - const guard = firefoxPopoutGuard(); - await TestBed.runInInjectionContext(() => guard(mockRoute, stateWithQuery)); - - expect(openPopoutSpy).toHaveBeenCalledWith("popup/index.html#/import?foo=bar&baz=qux"); - expect(closePopupSpy).toHaveBeenCalledWith(window); - }); - - it("should handle urls without query parameters", async () => { - const stateWithoutQuery: RouterStateSnapshot = { - url: "/simple-path", - } as RouterStateSnapshot; - - const guard = firefoxPopoutGuard(); - await TestBed.runInInjectionContext(() => guard(mockRoute, stateWithoutQuery)); - - expect(openPopoutSpy).toHaveBeenCalledWith("popup/index.html#/simple-path"); - expect(closePopupSpy).toHaveBeenCalledWith(window); - }); - }); -}); diff --git a/apps/browser/src/tools/popup/guards/firefox-popout.guard.ts b/apps/browser/src/tools/popup/guards/firefox-popout.guard.ts deleted file mode 100644 index 821f1b7a5bc..00000000000 --- a/apps/browser/src/tools/popup/guards/firefox-popout.guard.ts +++ /dev/null @@ -1,38 +0,0 @@ -import { ActivatedRouteSnapshot, CanActivateFn, RouterStateSnapshot } from "@angular/router"; - -import { BrowserApi } from "@bitwarden/browser/platform/browser/browser-api"; -import BrowserPopupUtils from "@bitwarden/browser/platform/browser/browser-popup-utils"; -import { BrowserPlatformUtilsService } from "@bitwarden/browser/platform/services/platform-utils/browser-platform-utils.service"; -import { DeviceType } from "@bitwarden/common/enums"; - -/** - * Guard that forces a popout window on Firefox browser when a file picker could be exposed. - * Necessary to avoid a crash: https://bugzilla.mozilla.org/show_bug.cgi?id=1292701 - * Also disallows the user from closing a popout and re-opening the view exposing the file picker. - * - * @returns CanActivateFn that opens popout and blocks navigation on Firefox - */ -export function firefoxPopoutGuard(): CanActivateFn { - return async (_route: ActivatedRouteSnapshot, state: RouterStateSnapshot) => { - // Check if browser is Firefox using the platform utils service - const deviceType = BrowserPlatformUtilsService.getDevice(window); - const isFirefox = deviceType === DeviceType.FirefoxExtension; - - // Check if already in popout/sidebar - const inPopout = BrowserPopupUtils.inPopout(window); - const inSidebar = BrowserPopupUtils.inSidebar(window); - - // Open popout if on Firefox and not already in popout/sidebar - if (isFirefox && !inPopout && !inSidebar) { - // Don't add autoClosePopout for file picker scenarios - user should manually close - await BrowserPopupUtils.openPopout(`popup/index.html#${state.url}`); - - // Close the original popup window - BrowserApi.closePopup(window); - - return false; // Block navigation - popout will reload - } - - return true; // Allow navigation - }; -} diff --git a/apps/browser/src/tools/popup/send-v2/send-file-popout-dialog/send-file-popout-dialog-container.component.ts b/apps/browser/src/tools/popup/send-v2/send-file-popout-dialog/send-file-popout-dialog-container.component.ts deleted file mode 100644 index e69de29bb2d..00000000000 diff --git a/apps/browser/src/vault/popup/components/vault/autofill-vault-list-items/autofill-vault-list-items.component.html b/apps/browser/src/vault/popup/components/vault/autofill-vault-list-items/autofill-vault-list-items.component.html index 38d60233200..8ea65e77c5e 100644 --- a/apps/browser/src/vault/popup/components/vault/autofill-vault-list-items/autofill-vault-list-items.component.html +++ b/apps/browser/src/vault/popup/components/vault/autofill-vault-list-items/autofill-vault-list-items.component.html @@ -6,8 +6,8 @@ (onRefresh)="refreshCurrentTab()" [description]="(showEmptyAutofillTip$ | async) ? ('autofillSuggestionsTip' | i18n) : undefined" isAutofillList + showAutofillButton [disableDescriptionMargin]="showEmptyAutofillTip$ | async" [groupByType]="groupByType()" - [showAutofillButton]="(clickItemsToAutofillVaultView$ | async) === false" [primaryActionAutofill]="clickItemsToAutofillVaultView$ | async" > diff --git a/apps/browser/src/vault/popup/components/vault/item-more-options/item-more-options.component.ts b/apps/browser/src/vault/popup/components/vault/item-more-options/item-more-options.component.ts index f7fe9ee1494..e564ca0ceea 100644 --- a/apps/browser/src/vault/popup/components/vault/item-more-options/item-more-options.component.ts +++ b/apps/browser/src/vault/popup/components/vault/item-more-options/item-more-options.component.ts @@ -218,6 +218,8 @@ export class ItemMoreOptionsComponent { return; } + //this tab checking should be moved into the vault-popup-autofill service in case the current tab is changed + //ticket: https://bitwarden.atlassian.net/browse/PM-32467 const currentTab = await firstValueFrom(this.vaultPopupAutofillService.currentAutofillTab$); if (!currentTab?.url) { diff --git a/apps/browser/src/vault/popup/components/vault/vault-list-items-container/vault-list-items-container.component.html b/apps/browser/src/vault/popup/components/vault/vault-list-items-container/vault-list-items-container.component.html index e9e89776dde..69c548540eb 100644 --- a/apps/browser/src/vault/popup/components/vault/vault-list-items-container/vault-list-items-container.component.html +++ b/apps/browser/src/vault/popup/components/vault/vault-list-items-container/vault-list-items-container.component.html @@ -90,7 +90,13 @@ - + } @if (showAutofillBadge()) { diff --git a/apps/browser/src/vault/popup/components/vault/vault-list-items-container/vault-list-items-container.component.ts b/apps/browser/src/vault/popup/components/vault/vault-list-items-container/vault-list-items-container.component.ts index fb8d20c5cf6..331ea799169 100644 --- a/apps/browser/src/vault/popup/components/vault/vault-list-items-container/vault-list-items-container.component.ts +++ b/apps/browser/src/vault/popup/components/vault/vault-list-items-container/vault-list-items-container.component.ts @@ -302,8 +302,9 @@ export class VaultListItemsContainerComponent implements AfterViewInit { if (this.currentUriIsBlocked()) { return false; } - return this.isAutofillList() - ? this.simplifiedItemActionEnabled() + + return this.simplifiedItemActionEnabled() + ? this.isAutofillList() : this.primaryActionAutofill(); }); diff --git a/apps/browser/src/vault/popup/components/vault/vault.component.html b/apps/browser/src/vault/popup/components/vault/vault.component.html index 28abb92b8a9..2f43d29d776 100644 --- a/apps/browser/src/vault/popup/components/vault/vault.component.html +++ b/apps/browser/src/vault/popup/components/vault/vault.component.html @@ -127,7 +127,7 @@ @if (cipher) { - + + @if (showAutofillButton()) { + + } + } diff --git a/apps/browser/src/vault/popup/components/vault/view/view.component.spec.ts b/apps/browser/src/vault/popup/components/vault/view/view.component.spec.ts index 5c94af0205d..af31dee7550 100644 --- a/apps/browser/src/vault/popup/components/vault/view/view.component.spec.ts +++ b/apps/browser/src/vault/popup/components/vault/view/view.component.spec.ts @@ -2,7 +2,7 @@ import { ComponentFixture, fakeAsync, flush, TestBed, tick } from "@angular/core import { By } from "@angular/platform-browser"; import { ActivatedRoute, Router } from "@angular/router"; import { mock } from "jest-mock-extended"; -import { of, Subject } from "rxjs"; +import { BehaviorSubject, of, Subject } from "rxjs"; import { CollectionService } from "@bitwarden/admin-console/common"; import { ApiService } from "@bitwarden/common/abstractions/api.service"; @@ -18,6 +18,7 @@ import { import { DomainSettingsService } from "@bitwarden/common/autofill/services/domain-settings.service"; import { BillingAccountProfileStateService } from "@bitwarden/common/billing/abstractions"; import { EventType } from "@bitwarden/common/enums"; +import { UriMatchStrategy } from "@bitwarden/common/models/domain/domain-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"; @@ -33,6 +34,7 @@ import { FolderService } from "@bitwarden/common/vault/abstractions/folder/folde import { CipherRepromptType, CipherType } from "@bitwarden/common/vault/enums"; import { CipherData } from "@bitwarden/common/vault/models/data/cipher.data"; import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view"; +import { LoginUriView } from "@bitwarden/common/vault/models/view/login-uri.view"; import { CipherAuthorizationService } from "@bitwarden/common/vault/services/cipher-authorization.service"; import { TaskService } from "@bitwarden/common/vault/tasks"; import { DialogService, ToastService } from "@bitwarden/components"; @@ -47,6 +49,10 @@ import BrowserPopupUtils from "../../../../../platform/browser/browser-popup-uti import { PopupRouterCacheService } from "../../../../../platform/popup/view-cache/popup-router-cache.service"; import { VaultPopupAutofillService } from "../../../services/vault-popup-autofill.service"; import { VaultPopupScrollPositionService } from "../../../services/vault-popup-scroll-position.service"; +import { + AutofillConfirmationDialogComponent, + AutofillConfirmationDialogResult, +} from "../autofill-confirmation-dialog/autofill-confirmation-dialog.component"; import { ViewComponent } from "./view.component"; @@ -62,6 +68,7 @@ describe("ViewComponent", () => { const mockNavigate = jest.fn(); const collect = jest.fn().mockResolvedValue(null); const doAutofill = jest.fn().mockResolvedValue(true); + const doAutofillAndSave = jest.fn().mockResolvedValue(true); const copy = jest.fn().mockResolvedValue(true); const back = jest.fn().mockResolvedValue(null); const openSimpleDialog = jest.fn().mockResolvedValue(true); @@ -69,6 +76,8 @@ describe("ViewComponent", () => { const showToast = jest.fn(); const showPasswordPrompt = jest.fn().mockResolvedValue(true); const getFeatureFlag$ = jest.fn().mockReturnValue(of(true)); + const getFeatureFlag = jest.fn().mockResolvedValue(true); + const currentAutofillTab$ = of({ url: "https://example.com", id: 1 }); const mockCipher = { id: "122-333-444", @@ -87,8 +96,12 @@ describe("ViewComponent", () => { const mockPasswordRepromptService = { showPasswordPrompt, }; + const autofillAllowed$ = new BehaviorSubject(true); const mockVaultPopupAutofillService = { doAutofill, + doAutofillAndSave, + currentAutofillTab$, + autofillAllowed$, }; const mockCopyCipherFieldService = { copy, @@ -112,12 +125,15 @@ describe("ViewComponent", () => { mockNavigate.mockClear(); collect.mockClear(); doAutofill.mockClear(); + doAutofillAndSave.mockClear(); copy.mockClear(); stop.mockClear(); openSimpleDialog.mockClear(); back.mockClear(); showToast.mockClear(); showPasswordPrompt.mockClear(); + getFeatureFlag.mockClear(); + autofillAllowed$.next(true); cipherArchiveService.hasArchiveFlagEnabled$ = of(true); cipherArchiveService.userCanArchive$.mockReturnValue(of(false)); cipherArchiveService.archiveWithServer.mockResolvedValue({ id: "122-333-444" } as CipherData); @@ -137,7 +153,7 @@ describe("ViewComponent", () => { { provide: VaultPopupScrollPositionService, useValue: { stop } }, { provide: VaultPopupAutofillService, useValue: mockVaultPopupAutofillService }, { provide: ToastService, useValue: { showToast } }, - { provide: ConfigService, useValue: { getFeatureFlag$ } }, + { provide: ConfigService, useValue: { getFeatureFlag$, getFeatureFlag } }, { provide: I18nService, useValue: { @@ -203,6 +219,8 @@ describe("ViewComponent", () => { provide: DomainSettingsService, useValue: { showFavicons$: of(true), + resolvedDefaultUriMatchStrategy$: of(UriMatchStrategy.Domain), + getUrlEquivalentDomains: jest.fn().mockReturnValue(of([])), }, }, { @@ -697,4 +715,452 @@ describe("ViewComponent", () => { expect(badge).toBeFalsy(); }); }); + + describe("showAutofillButton", () => { + beforeEach(() => { + component.cipher = { ...mockCipher, type: CipherType.Login } as CipherView; + }); + + it("returns true when feature flag is enabled, cipher is a login, and not archived/deleted", fakeAsync(() => { + getFeatureFlag$.mockReturnValue(of(true)); + autofillAllowed$.next(true); + + // Recreate component to pick up the signal values + fixture = TestBed.createComponent(ViewComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + + component.cipher = { + ...mockCipher, + type: CipherType.Login, + isArchived: false, + isDeleted: false, + } as CipherView; + + flush(); + + const result = component.showAutofillButton(); + + expect(result).toBe(true); + })); + + it("returns true for Card type when conditions are met", fakeAsync(() => { + getFeatureFlag$.mockReturnValue(of(true)); + autofillAllowed$.next(true); + + // Recreate component to pick up the signal values + fixture = TestBed.createComponent(ViewComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + + component.cipher = { + ...mockCipher, + type: CipherType.Card, + isArchived: false, + isDeleted: false, + } as CipherView; + + flush(); + + const result = component.showAutofillButton(); + + expect(result).toBe(true); + })); + + it("returns true for Identity type when conditions are met", fakeAsync(() => { + getFeatureFlag$.mockReturnValue(of(true)); + autofillAllowed$.next(true); + + // Recreate component to pick up the signal values + fixture = TestBed.createComponent(ViewComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + + component.cipher = { + ...mockCipher, + type: CipherType.Identity, + isArchived: false, + isDeleted: false, + } as CipherView; + + flush(); + + const result = component.showAutofillButton(); + + expect(result).toBe(true); + })); + + it("returns false when feature flag is disabled", fakeAsync(() => { + getFeatureFlag$.mockReturnValue(of(false)); + + // Recreate component to pick up the new feature flag value + fixture = TestBed.createComponent(ViewComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + + component.cipher = { + ...mockCipher, + type: CipherType.Login, + isArchived: false, + isDeleted: false, + } as CipherView; + + flush(); + + const result = component.showAutofillButton(); + + expect(result).toBe(false); + })); + + it("returns false when autofill is not allowed", fakeAsync(() => { + getFeatureFlag$.mockReturnValue(of(true)); + autofillAllowed$.next(false); + + // Recreate component to pick up the new autofillAllowed value + fixture = TestBed.createComponent(ViewComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + + component.cipher = { + ...mockCipher, + type: CipherType.Login, + isArchived: false, + isDeleted: false, + } as CipherView; + + flush(); + + const result = component.showAutofillButton(); + + expect(result).toBe(false); + })); + + it("returns false for SecureNote type", fakeAsync(() => { + getFeatureFlag$.mockReturnValue(of(true)); + autofillAllowed$.next(true); + + // Recreate component to pick up the signal values + fixture = TestBed.createComponent(ViewComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + + component.cipher = { + ...mockCipher, + type: CipherType.SecureNote, + isArchived: false, + isDeleted: false, + } as CipherView; + + flush(); + + const result = component.showAutofillButton(); + + expect(result).toBe(false); + })); + + it("returns false for SshKey type", fakeAsync(() => { + getFeatureFlag$.mockReturnValue(of(true)); + autofillAllowed$.next(true); + + // Recreate component to pick up the signal values + fixture = TestBed.createComponent(ViewComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + + component.cipher = { + ...mockCipher, + type: CipherType.SshKey, + isArchived: false, + isDeleted: false, + } as CipherView; + + flush(); + + const result = component.showAutofillButton(); + + expect(result).toBe(false); + })); + + it("returns false when cipher is archived", fakeAsync(() => { + getFeatureFlag$.mockReturnValue(of(true)); + autofillAllowed$.next(true); + + // Recreate component to pick up the signal values + fixture = TestBed.createComponent(ViewComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + + component.cipher = { + ...mockCipher, + type: CipherType.Login, + isArchived: true, + isDeleted: false, + } as CipherView; + + flush(); + + const result = component.showAutofillButton(); + + expect(result).toBe(false); + })); + + it("returns false when cipher is deleted", fakeAsync(() => { + getFeatureFlag$.mockReturnValue(of(true)); + autofillAllowed$.next(true); + + // Recreate component to pick up the signal values + fixture = TestBed.createComponent(ViewComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + + component.cipher = { + ...mockCipher, + type: CipherType.Login, + isArchived: false, + isDeleted: true, + } as CipherView; + + flush(); + + const result = component.showAutofillButton(); + + expect(result).toBe(false); + })); + }); + + describe("doAutofill", () => { + let dialogService: DialogService; + const originalCurrentAutofillTab$ = currentAutofillTab$; + + beforeEach(() => { + dialogService = TestBed.inject(DialogService); + + component.cipher = { + ...mockCipher, + type: CipherType.Login, + login: { + username: "test", + password: "test", + uris: [ + { + uri: "https://example.com", + match: null, + } as LoginUriView, + ], + }, + edit: true, + } as CipherView; + }); + + afterEach(() => { + // Restore original observable to prevent test pollution + mockVaultPopupAutofillService.currentAutofillTab$ = originalCurrentAutofillTab$; + }); + + it("returns early when feature flag is disabled", async () => { + getFeatureFlag.mockResolvedValue(false); + + await component.doAutofill(); + + expect(doAutofill).not.toHaveBeenCalled(); + expect(openSimpleDialog).not.toHaveBeenCalled(); + }); + + it("shows exact match dialog when no URIs and default strategy is Exact", async () => { + getFeatureFlag.mockResolvedValue(true); + component.cipher.login.uris = []; + (component as any).uriMatchStrategy$ = of(UriMatchStrategy.Exact); + + await component.doAutofill(); + + expect(openSimpleDialog).toHaveBeenCalledWith({ + title: { key: "cannotAutofill" }, + content: { key: "cannotAutofillExactMatch" }, + type: "info", + acceptButtonText: { key: "okay" }, + cancelButtonText: null, + }); + expect(doAutofill).not.toHaveBeenCalled(); + }); + + it("shows exact match dialog when all URIs have exact match strategy", async () => { + getFeatureFlag.mockResolvedValue(true); + component.cipher.login.uris = [ + { uri: "https://example.com", match: UriMatchStrategy.Exact } as LoginUriView, + { uri: "https://example2.com", match: UriMatchStrategy.Exact } as LoginUriView, + ]; + + await component.doAutofill(); + + expect(openSimpleDialog).toHaveBeenCalledWith({ + title: { key: "cannotAutofill" }, + content: { key: "cannotAutofillExactMatch" }, + type: "info", + acceptButtonText: { key: "okay" }, + cancelButtonText: null, + }); + expect(doAutofill).not.toHaveBeenCalled(); + }); + + it("shows error dialog when current tab URL is unavailable", async () => { + getFeatureFlag.mockResolvedValue(true); + mockVaultPopupAutofillService.currentAutofillTab$ = of({ url: null, id: 1 }); + + await component.doAutofill(); + + expect(openSimpleDialog).toHaveBeenCalledWith({ + title: { key: "error" }, + content: { key: "errorGettingAutoFillData" }, + type: "danger", + }); + expect(doAutofill).not.toHaveBeenCalled(); + }); + + it("autofills directly when domain matches", async () => { + getFeatureFlag.mockResolvedValue(true); + jest.spyOn(component as any, "_domainMatched").mockResolvedValue(true); + + await component.doAutofill(); + + expect(doAutofill).toHaveBeenCalledWith(component.cipher, true, true); + }); + + it("shows confirmation dialog when domain does not match", async () => { + getFeatureFlag.mockResolvedValue(true); + jest.spyOn(component as any, "_domainMatched").mockResolvedValue(false); + + const mockDialogRef = { + closed: of(AutofillConfirmationDialogResult.Canceled), + }; + jest.spyOn(AutofillConfirmationDialogComponent, "open").mockReturnValue(mockDialogRef as any); + + await component.doAutofill(); + + expect(AutofillConfirmationDialogComponent.open).toHaveBeenCalledWith(dialogService, { + data: { + currentUrl: "https://example.com", + savedUrls: ["https://example.com"], + viewOnly: false, + }, + }); + }); + + it("does not autofill when user cancels confirmation dialog", async () => { + getFeatureFlag.mockResolvedValue(true); + jest.spyOn(component as any, "_domainMatched").mockResolvedValue(false); + + const mockDialogRef = { + closed: of(AutofillConfirmationDialogResult.Canceled), + }; + jest.spyOn(AutofillConfirmationDialogComponent, "open").mockReturnValue(mockDialogRef as any); + + await component.doAutofill(); + + expect(doAutofill).not.toHaveBeenCalled(); + expect(doAutofillAndSave).not.toHaveBeenCalled(); + }); + + it("autofills only when user selects AutofilledOnly", async () => { + getFeatureFlag.mockResolvedValue(true); + jest.spyOn(component as any, "_domainMatched").mockResolvedValue(false); + + const mockDialogRef = { + closed: of(AutofillConfirmationDialogResult.AutofilledOnly), + }; + jest.spyOn(AutofillConfirmationDialogComponent, "open").mockReturnValue(mockDialogRef as any); + + await component.doAutofill(); + + expect(doAutofill).toHaveBeenCalledWith(component.cipher, true, true); + expect(doAutofillAndSave).not.toHaveBeenCalled(); + }); + + it("autofills and saves URL when user selects AutofillAndUrlAdded", async () => { + getFeatureFlag.mockResolvedValue(true); + jest.spyOn(component as any, "_domainMatched").mockResolvedValue(false); + + const mockDialogRef = { + closed: of(AutofillConfirmationDialogResult.AutofillAndUrlAdded), + }; + jest.spyOn(AutofillConfirmationDialogComponent, "open").mockReturnValue(mockDialogRef as any); + + await component.doAutofill(); + + expect(doAutofillAndSave).toHaveBeenCalledWith(component.cipher, true, true); + expect(doAutofill).not.toHaveBeenCalled(); + }); + + it("passes viewOnly as true when cipher is not editable", async () => { + getFeatureFlag.mockResolvedValue(true); + jest.spyOn(component as any, "_domainMatched").mockResolvedValue(false); + component.cipher.edit = false; + + const mockDialogRef = { + closed: of(AutofillConfirmationDialogResult.Canceled), + }; + const openSpy = jest + .spyOn(AutofillConfirmationDialogComponent, "open") + .mockReturnValue(mockDialogRef as any); + + await component.doAutofill(); + + expect(openSpy).toHaveBeenCalledWith(dialogService, { + data: { + currentUrl: "https://example.com", + savedUrls: ["https://example.com"], + viewOnly: true, + }, + }); + }); + + it("filters out URIs without uri property", async () => { + getFeatureFlag.mockResolvedValue(true); + jest.spyOn(component as any, "_domainMatched").mockResolvedValue(false); + component.cipher.login.uris = [ + { uri: "https://example.com" } as LoginUriView, + { uri: null } as LoginUriView, + { uri: "https://example2.com" } as LoginUriView, + ]; + + const mockDialogRef = { + closed: of(AutofillConfirmationDialogResult.Canceled), + }; + const openSpy = jest + .spyOn(AutofillConfirmationDialogComponent, "open") + .mockReturnValue(mockDialogRef as any); + + await component.doAutofill(); + + expect(openSpy).toHaveBeenCalledWith(dialogService, { + data: { + currentUrl: "https://example.com", + savedUrls: ["https://example.com", "https://example2.com"], + viewOnly: false, + }, + }); + }); + + it("handles cipher with no login uris gracefully", async () => { + getFeatureFlag.mockResolvedValue(true); + jest.spyOn(component as any, "_domainMatched").mockResolvedValue(false); + component.cipher.login.uris = null; + + const mockDialogRef = { + closed: of(AutofillConfirmationDialogResult.Canceled), + }; + const openSpy = jest + .spyOn(AutofillConfirmationDialogComponent, "open") + .mockReturnValue(mockDialogRef as any); + + await component.doAutofill(); + + expect(openSpy).toHaveBeenCalledWith(dialogService, { + data: { + currentUrl: "https://example.com", + savedUrls: [], + viewOnly: false, + }, + }); + }); + }); }); diff --git a/apps/browser/src/vault/popup/components/vault/view/view.component.ts b/apps/browser/src/vault/popup/components/vault/view/view.component.ts index 48402a957d6..5166dbcf8db 100644 --- a/apps/browser/src/vault/popup/components/vault/view/view.component.ts +++ b/apps/browser/src/vault/popup/components/vault/view/view.component.ts @@ -2,7 +2,7 @@ // @ts-strict-ignore import { CommonModule } from "@angular/common"; import { Component } from "@angular/core"; -import { takeUntilDestroyed } from "@angular/core/rxjs-interop"; +import { takeUntilDestroyed, toSignal } from "@angular/core/rxjs-interop"; import { FormsModule } from "@angular/forms"; import { ActivatedRoute, Router } from "@angular/router"; import { firstValueFrom, Observable, switchMap, of, map } from "rxjs"; @@ -21,7 +21,11 @@ import { SHOW_AUTOFILL_BUTTON, UPDATE_PASSWORD, } from "@bitwarden/common/autofill/constants"; +import { DomainSettingsService } from "@bitwarden/common/autofill/services/domain-settings.service"; import { EventType } from "@bitwarden/common/enums"; +import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum"; +import { UriMatchStrategy } from "@bitwarden/common/models/domain/domain-service"; +import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; import { UserId } from "@bitwarden/common/types/guid"; @@ -32,6 +36,7 @@ import { ViewPasswordHistoryService } from "@bitwarden/common/vault/abstractions import { CipherRepromptType, CipherType } from "@bitwarden/common/vault/enums"; import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view"; import { CipherAuthorizationService } from "@bitwarden/common/vault/services/cipher-authorization.service"; +import { CipherViewLikeUtils } from "@bitwarden/common/vault/utils/cipher-view-like-utils"; import { filterOutNullish } from "@bitwarden/common/vault/utils/observable-utilities"; import { AsyncActionsModule, @@ -66,6 +71,10 @@ import { VaultPopupAutofillService } from "../../../services/vault-popup-autofil import { VaultPopupScrollPositionService } from "../../../services/vault-popup-scroll-position.service"; import { closeViewVaultItemPopout, VaultPopoutType } from "../../../utils/vault-popout-window"; import { ROUTES_AFTER_EDIT_DELETION } from "../add-edit/add-edit.component"; +import { + AutofillConfirmationDialogComponent, + AutofillConfirmationDialogResult, +} from "../autofill-confirmation-dialog/autofill-confirmation-dialog.component"; /** * The types of actions that can be triggered when loading the view vault item popout via the @@ -118,6 +127,13 @@ export class ViewComponent { senderTabId?: number; routeAfterDeletion?: ROUTES_AFTER_EDIT_DELETION; + //feature flag + private readonly pm30521FeatureFlag = toSignal( + this.configService.getFeatureFlag$(FeatureFlag.PM30521_AutofillButtonViewLoginScreen), + ); + + private readonly autofillAllowed = toSignal(this.vaultPopupAutofillService.autofillAllowed$); + private uriMatchStrategy$ = this.domainSettingsService.resolvedDefaultUriMatchStrategy$; protected showFooter$: Observable; protected userCanArchive$ = this.accountService.activeAccount$ .pipe(getUserId) @@ -142,6 +158,8 @@ export class ViewComponent { private popupScrollPositionService: VaultPopupScrollPositionService, private archiveService: CipherArchiveService, private archiveCipherUtilsService: ArchiveCipherUtilitiesService, + private domainSettingsService: DomainSettingsService, + private configService: ConfigService, ) { this.subscribeToParams(); } @@ -322,6 +340,113 @@ export class ViewComponent { : this.cipherService.softDeleteWithServer(this.cipher.id, this.activeUserId); } + showAutofillButton(): boolean { + //feature flag + if (!this.pm30521FeatureFlag()) { + return false; + } + + if (!this.autofillAllowed()) { + return false; + } + + const validAutofillType = ( + [CipherType.Login, CipherType.Card, CipherType.Identity] as CipherType[] + ).includes(CipherViewLikeUtils.getType(this.cipher)); + + return validAutofillType && !(this.cipher.isArchived || this.cipher.isDeleted); + } + + async doAutofill() { + //feature flag + if ( + !(await this.configService.getFeatureFlag(FeatureFlag.PM30521_AutofillButtonViewLoginScreen)) + ) { + return; + } + + //for non login types that are still auto-fillable + if (CipherViewLikeUtils.getType(this.cipher) !== CipherType.Login) { + await this.vaultPopupAutofillService.doAutofill(this.cipher, true, true); + return; + } + + const uris = this.cipher.login?.uris ?? []; + const uriMatchStrategy = await firstValueFrom(this.uriMatchStrategy$); + + const showExactMatchDialog = + uris.length === 0 + ? uriMatchStrategy === UriMatchStrategy.Exact + : // all saved URIs are exact match + uris.every((u) => (u.match ?? uriMatchStrategy) === UriMatchStrategy.Exact); + + if (showExactMatchDialog) { + await this.dialogService.openSimpleDialog({ + title: { key: "cannotAutofill" }, + content: { key: "cannotAutofillExactMatch" }, + type: "info", + acceptButtonText: { key: "okay" }, + cancelButtonText: null, + }); + return; + } + + //this tab checking should be moved into the vault-popup-autofill service in case the current tab is changed + //ticket: https://bitwarden.atlassian.net/browse/PM-32467 + const currentTab = await firstValueFrom(this.vaultPopupAutofillService.currentAutofillTab$); + + if (!currentTab?.url) { + await this.dialogService.openSimpleDialog({ + title: { key: "error" }, + content: { key: "errorGettingAutoFillData" }, + type: "danger", + }); + return; + } + + if (await this._domainMatched(currentTab)) { + await this.vaultPopupAutofillService.doAutofill(this.cipher, true, true); + return; + } + + const ref = AutofillConfirmationDialogComponent.open(this.dialogService, { + data: { + currentUrl: currentTab?.url || "", + savedUrls: this.cipher.login?.uris?.filter((u) => u.uri).map((u) => u.uri!) ?? [], + viewOnly: !this.cipher.edit, + }, + }); + + const result = await firstValueFrom(ref.closed); + + switch (result) { + case AutofillConfirmationDialogResult.Canceled: + return; + case AutofillConfirmationDialogResult.AutofilledOnly: + await this.vaultPopupAutofillService.doAutofill(this.cipher, true, true); + return; + case AutofillConfirmationDialogResult.AutofillAndUrlAdded: + await this.vaultPopupAutofillService.doAutofillAndSave(this.cipher, true, true); + return; + } + } + + private async _domainMatched(currentTab: chrome.tabs.Tab): Promise { + const equivalentDomains = await firstValueFrom( + this.domainSettingsService.getUrlEquivalentDomains(currentTab?.url), + ); + const defaultMatch = await firstValueFrom( + this.domainSettingsService.resolvedDefaultUriMatchStrategy$, + ); + + return CipherViewLikeUtils.matchesUri( + this.cipher, + currentTab?.url, + equivalentDomains, + defaultMatch, + ); + } + /** * Handles the load action for the view vault item popout. These actions are typically triggered * via the extension context menu. It is necessary to render the view for items that have password diff --git a/apps/browser/src/vault/popup/services/vault-popup-items.service.spec.ts b/apps/browser/src/vault/popup/services/vault-popup-items.service.spec.ts index 845dfd6f4b1..79b0cc63a4b 100644 --- a/apps/browser/src/vault/popup/services/vault-popup-items.service.spec.ts +++ b/apps/browser/src/vault/popup/services/vault-popup-items.service.spec.ts @@ -370,37 +370,6 @@ describe("VaultPopupItemsService", () => { }); }); - describe("remainingCiphers$", () => { - beforeEach(() => { - searchService.isSearchable.mockImplementation(async (text) => text.length > 2); - }); - - it("should exclude autofill and favorite ciphers", (done) => { - service.remainingCiphers$.subscribe((ciphers) => { - // 2 autofill ciphers, 2 favorite ciphers = 6 remaining ciphers to show - expect(ciphers.length).toBe(6); - done(); - }); - }); - - it("should filter remainingCiphers$ down to search term", (done) => { - const cipherList = Object.values(allCiphers); - const searchText = "Login"; - - searchService.searchCiphers.mockImplementation(async () => { - return cipherList.filter((cipher) => { - return cipher.name.includes(searchText); - }); - }); - - service.remainingCiphers$.subscribe((ciphers) => { - // There are 6 remaining ciphers but only 2 with "Login" in the name - expect(ciphers.length).toBe(2); - done(); - }); - }); - }); - describe("emptyVault$", () => { it("should return true if there are no ciphers", (done) => { cipherServiceMock.cipherListViews$.mockReturnValue(of([])); @@ -493,8 +462,8 @@ describe("VaultPopupItemsService", () => { // Start tracking loading$ emissions tracked = new ObservableTracker(service.loading$); - // Track remainingCiphers$ to make cipher observables active - trackedCiphers = new ObservableTracker(service.remainingCiphers$); + // Track favoriteCiphers$ to make cipher observables active + trackedCiphers = new ObservableTracker(service.favoriteCiphers$); }); it("should initialize with true first", async () => { diff --git a/apps/browser/src/vault/popup/services/vault-popup-items.service.ts b/apps/browser/src/vault/popup/services/vault-popup-items.service.ts index 016fa330a38..0055d683f22 100644 --- a/apps/browser/src/vault/popup/services/vault-popup-items.service.ts +++ b/apps/browser/src/vault/popup/services/vault-popup-items.service.ts @@ -2,7 +2,6 @@ import { inject, Injectable, NgZone } from "@angular/core"; import { toObservable } from "@angular/core/rxjs-interop"; import { combineLatest, - concatMap, distinctUntilChanged, distinctUntilKeyChanged, filter, @@ -242,31 +241,12 @@ export class VaultPopupItemsService { shareReplay({ refCount: false, bufferSize: 1 }), ); - /** - * List of all remaining ciphers that are not currently suggested for autofill or marked as favorite. - * Ciphers are sorted by name. - */ - remainingCiphers$: Observable = this.favoriteCiphers$.pipe( - concatMap( - ( - favoriteCiphers, // concatMap->of is used to make withLatestFrom lazy to avoid race conditions with autoFillCiphers$ - ) => - of(favoriteCiphers).pipe(withLatestFrom(this._filteredCipherList$, this.autoFillCiphers$)), - ), - map(([favoriteCiphers, ciphers, autoFillCiphers]) => - ciphers.filter( - (cipher) => !autoFillCiphers.includes(cipher) && !favoriteCiphers.includes(cipher), - ), - ), - shareReplay({ refCount: false, bufferSize: 1 }), - ); - /** * Observable that indicates whether the service is currently loading ciphers. */ loading$: Observable = merge( this._ciphersLoading$.pipe(map(() => true)), - this.remainingCiphers$.pipe(map(() => false)), + this.favoriteCiphers$.pipe(map(() => false)), ).pipe(startWith(true), distinctUntilChanged(), shareReplay({ refCount: false, bufferSize: 1 })); /** Observable that indicates whether there is search text present. diff --git a/apps/browser/src/vault/popup/settings/admin-settings.component.spec.ts b/apps/browser/src/vault/popup/settings/admin-settings.component.spec.ts index f7b4e7b473a..2a9ebdcddf6 100644 --- a/apps/browser/src/vault/popup/settings/admin-settings.component.spec.ts +++ b/apps/browser/src/vault/popup/settings/admin-settings.component.spec.ts @@ -7,6 +7,8 @@ import { of } from "rxjs"; import { NudgesService, NudgeType } from "@bitwarden/angular/vault"; import { AutoConfirmState, AutomaticUserConfirmationService } from "@bitwarden/auto-confirm"; import { PopOutComponent } from "@bitwarden/browser/platform/popup/components/pop-out.component"; +import { EventCollectionService } from "@bitwarden/common/abstractions/event/event-collection.service"; +import { InternalOrganizationServiceAbstraction } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction"; import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { mockAccountServiceWith } from "@bitwarden/common/spec"; @@ -52,6 +54,8 @@ describe("AdminSettingsComponent", () => { let autoConfirmService: MockProxy; let nudgesService: MockProxy; let mockDialogService: MockProxy; + let eventCollectionService: MockProxy; + let organizationService: MockProxy; const userId = "test-user-id" as UserId; const mockAutoConfirmState: AutoConfirmState = { @@ -64,10 +68,14 @@ describe("AdminSettingsComponent", () => { autoConfirmService = mock(); nudgesService = mock(); mockDialogService = mock(); + eventCollectionService = mock(); + organizationService = mock(); autoConfirmService.configuration$.mockReturnValue(of(mockAutoConfirmState)); autoConfirmService.upsert.mockResolvedValue(undefined); nudgesService.showNudgeSpotlight$.mockReturnValue(of(false)); + eventCollectionService.collect.mockResolvedValue(undefined); + organizationService.organizations$.mockReturnValue(of([])); await TestBed.configureTestingModule({ imports: [AdminSettingsComponent], @@ -77,6 +85,11 @@ describe("AdminSettingsComponent", () => { { provide: AutomaticUserConfirmationService, useValue: autoConfirmService }, { provide: DialogService, useValue: mockDialogService }, { provide: NudgesService, useValue: nudgesService }, + { provide: EventCollectionService, useValue: eventCollectionService }, + { + provide: InternalOrganizationServiceAbstraction, + useValue: organizationService, + }, { provide: I18nService, useValue: { t: (key: string) => key } }, ], }) diff --git a/apps/browser/src/vault/popup/settings/admin-settings.component.ts b/apps/browser/src/vault/popup/settings/admin-settings.component.ts index 52da4318047..99cb5a814c1 100644 --- a/apps/browser/src/vault/popup/settings/admin-settings.component.ts +++ b/apps/browser/src/vault/popup/settings/admin-settings.component.ts @@ -20,8 +20,11 @@ import { import { PopOutComponent } from "@bitwarden/browser/platform/popup/components/pop-out.component"; import { PopupHeaderComponent } from "@bitwarden/browser/platform/popup/layout/popup-header.component"; import { PopupPageComponent } from "@bitwarden/browser/platform/popup/layout/popup-page.component"; +import { EventCollectionService } from "@bitwarden/common/abstractions/event/event-collection.service"; +import { InternalOrganizationServiceAbstraction } 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 { EventType } from "@bitwarden/common/enums"; import { BitIconButtonComponent, CardComponent, @@ -69,6 +72,8 @@ export class AdminSettingsComponent implements OnInit { private destroyRef: DestroyRef, private dialogService: DialogService, private nudgesService: NudgesService, + private eventCollectionService: EventCollectionService, + private organizationService: InternalOrganizationServiceAbstraction, ) {} async ngOnInit() { @@ -88,14 +93,26 @@ export class AdminSettingsComponent implements OnInit { } return of(false); }), - withLatestFrom(this.autoConfirmService.configuration$(userId)), - switchMap(([newValue, existingState]) => - this.autoConfirmService.upsert(userId, { + withLatestFrom( + this.autoConfirmService.configuration$(userId), + this.organizationService.organizations$(userId), + ), + switchMap(async ([newValue, existingState, organizations]) => { + await this.autoConfirmService.upsert(userId, { ...existingState, enabled: newValue, showBrowserNotification: false, - }), - ), + }); + + // Auto-confirm users can only belong to one organization + const organization = organizations[0]; + if (organization?.id) { + const eventType = newValue + ? EventType.Organization_AutoConfirmEnabled_Admin + : EventType.Organization_AutoConfirmDisabled_Admin; + await this.eventCollectionService.collect(eventType, undefined, true, organization.id); + } + }), takeUntilDestroyed(this.destroyRef), ) .subscribe(); diff --git a/apps/browser/src/vault/popup/settings/folders.component.spec.ts b/apps/browser/src/vault/popup/settings/folders.component.spec.ts index 678e6d3f10e..7e08cc684a1 100644 --- a/apps/browser/src/vault/popup/settings/folders.component.spec.ts +++ b/apps/browser/src/vault/popup/settings/folders.component.spec.ts @@ -94,11 +94,12 @@ describe("FoldersComponent", () => { fixture.detectChanges(); }); - it("removes the last option in the folder array", (done) => { + it("should show all folders", (done) => { component.folders$.subscribe((folders) => { expect(folders).toEqual([ { id: "1", name: "Folder 1" }, { id: "2", name: "Folder 2" }, + { id: "0", name: "No Folder" }, ]); done(); }); diff --git a/apps/browser/src/vault/popup/settings/folders.component.ts b/apps/browser/src/vault/popup/settings/folders.component.ts index b70c17bd6a5..a38f6630949 100644 --- a/apps/browser/src/vault/popup/settings/folders.component.ts +++ b/apps/browser/src/vault/popup/settings/folders.component.ts @@ -53,13 +53,6 @@ export class FoldersComponent { this.folders$ = this.activeUserId$.pipe( filter((userId): userId is UserId => userId !== null), switchMap((userId) => this.folderService.folderViews$(userId)), - map((folders) => { - // Remove the last folder, which is the "no folder" option folder - if (folders.length > 0) { - return folders.slice(0, folders.length - 1); - } - return folders; - }), ); } diff --git a/apps/browser/store/locales/sl/copy.resx b/apps/browser/store/locales/sl/copy.resx index b2a95ed5689..07864dcc1f1 100644 --- a/apps/browser/store/locales/sl/copy.resx +++ b/apps/browser/store/locales/sl/copy.resx @@ -118,10 +118,10 @@ System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - Bitwarden Password Manager + Bitwarden – Upravitelj gesel - At home, at work, or on the go, Bitwarden easily secures all your passwords, passkeys, and sensitive information. + Doma, na delu ali na poti – Bitwarden na enostaven način zaščiti vsa vaša gesla, ključe za dostop in občutljive podatke. Recognized as the best password manager by PCMag, WIRED, The Verge, CNET, G2, and more! @@ -169,7 +169,7 @@ End-to-end encrypted credential management solutions from Bitwarden empower orga - At home, at work, or on the go, Bitwarden easily secures all your passwords, passkeys, and sensitive information. + Doma, na delu ali na poti – Bitwarden na enostaven način zaščiti vsa vaša gesla, ključe za dostop in občutljive podatke. Sinhronizirajte svoj trezor gesel in dostopajte do njega z več naprav diff --git a/apps/cli/package.json b/apps/cli/package.json index 6c27267054f..a5b3a00ec4e 100644 --- a/apps/cli/package.json +++ b/apps/cli/package.json @@ -1,7 +1,7 @@ { "name": "@bitwarden/cli", "description": "A secure and free password manager for all of your devices.", - "version": "2026.1.0", + "version": "2026.2.0", "keywords": [ "bitwarden", "password", diff --git a/apps/cli/src/commands/get.command.ts b/apps/cli/src/commands/get.command.ts index db070344628..cc01c21e0d0 100644 --- a/apps/cli/src/commands/get.command.ts +++ b/apps/cli/src/commands/get.command.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { filter, firstValueFrom, map, switchMap } from "rxjs"; import { CollectionService } from "@bitwarden/admin-console/common"; diff --git a/apps/cli/src/locales/en/messages.json b/apps/cli/src/locales/en/messages.json index 18079bd2409..824b03b99cf 100644 --- a/apps/cli/src/locales/en/messages.json +++ b/apps/cli/src/locales/en/messages.json @@ -35,6 +35,9 @@ "invalidVerificationCode": { "message": "Invalid verification code." }, + "invalidEmailOrVerificationCode": { + "message": "Invalid email or verification code" + }, "masterPassRequired": { "message": "Master password is required." }, diff --git a/apps/cli/src/service-container/service-container.ts b/apps/cli/src/service-container/service-container.ts index b5a2b1b8196..ffc67fac9d9 100644 --- a/apps/cli/src/service-container/service-container.ts +++ b/apps/cli/src/service-container/service-container.ts @@ -664,7 +664,7 @@ export class ServiceContainer { this.accountService, this.kdfConfigService, this.keyService, - this.securityStateService, + this.accountCryptographicStateService, this.apiService, this.stateProvider, this.configService, diff --git a/apps/cli/src/utils.ts b/apps/cli/src/utils.ts index 72746cb9b71..3aafcea6346 100644 --- a/apps/cli/src/utils.ts +++ b/apps/cli/src/utils.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import * as fs from "fs"; import * as path from "path"; diff --git a/apps/cli/src/vault/delete.command.ts b/apps/cli/src/vault/delete.command.ts index 8df1f8f316e..c92379e058f 100644 --- a/apps/cli/src/vault/delete.command.ts +++ b/apps/cli/src/vault/delete.command.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { firstValueFrom } from "rxjs"; import { ApiService } from "@bitwarden/common/abstractions/api.service"; diff --git a/apps/cli/src/vault/models/attachment.response.ts b/apps/cli/src/vault/models/attachment.response.ts index c4450fa8def..c7e88df1c39 100644 --- a/apps/cli/src/vault/models/attachment.response.ts +++ b/apps/cli/src/vault/models/attachment.response.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { AttachmentView } from "@bitwarden/common/vault/models/view/attachment.view"; export class AttachmentResponse { diff --git a/apps/desktop/custom-appx-manifest.xml b/apps/desktop/custom-appx-manifest.xml index 166b852588b..8a5c36e7da6 100644 --- a/apps/desktop/custom-appx-manifest.xml +++ b/apps/desktop/custom-appx-manifest.xml @@ -1,17 +1,9 @@ - + xmlns:desktop="http://schemas.microsoft.com/appx/manifest/desktop/windows10" + xmlns:rescap="http://schemas.microsoft.com/appx/manifest/foundation/windows10/restrictedcapabilities"> + @@ -87,8 +80,9 @@ xmlns:rescap="http://schemas.microsoft.com/appx/manifest/foundation/windows10/re - + + @@ -106,6 +100,13 @@ xmlns:rescap="http://schemas.microsoft.com/appx/manifest/foundation/windows10/re + + + + Bitwarden + + + diff --git a/apps/desktop/desktop_native/core/src/biometric/unix.rs b/apps/desktop/desktop_native/core/src/biometric/unix.rs index 3f4f10a1fcf..f120408a9e5 100644 --- a/apps/desktop/desktop_native/core/src/biometric/unix.rs +++ b/apps/desktop/desktop_native/core/src/biometric/unix.rs @@ -21,7 +21,32 @@ impl super::BiometricTrait for Biometric { async fn prompt(_hwnd: Vec, _message: String) -> Result { let connection = Connection::system().await?; let proxy = AuthorityProxy::new(&connection).await?; - let subject = Subject::new_for_owner(std::process::id(), None, None)?; + + // Use system-bus-name instead of unix-process to avoid PID namespace issues in + // sandboxed environments (e.g., Flatpak). When using unix-process with a PID from + // inside the sandbox, polkit cannot validate it against the host PID namespace. + // + // By using system-bus-name, polkit queries D-Bus for the connection's credentials, + // which includes the correct host PID and UID, avoiding namespace mismatches. + // + // If D-Bus unique name is not available, fall back to the traditional unix-process + // approach for compatibility with non-sandboxed environments. + let subject = if let Some(bus_name) = connection.unique_name() { + use zbus::zvariant::{OwnedValue, Str}; + let mut subject_details = std::collections::HashMap::new(); + subject_details.insert( + "name".to_string(), + OwnedValue::from(Str::from(bus_name.as_str())), + ); + Subject { + subject_kind: "system-bus-name".to_string(), + subject_details, + } + } else { + // Fallback: use unix-process with PID (may not work in sandboxed environments) + Subject::new_for_owner(std::process::id(), None, None)? + }; + let details = std::collections::HashMap::new(); let result = proxy .check_authorization( diff --git a/apps/desktop/desktop_native/core/src/biometric_v2/linux.rs b/apps/desktop/desktop_native/core/src/biometric_v2/linux.rs index ef6527e7b26..2656bd3fdf9 100644 --- a/apps/desktop/desktop_native/core/src/biometric_v2/linux.rs +++ b/apps/desktop/desktop_native/core/src/biometric_v2/linux.rs @@ -96,7 +96,32 @@ async fn polkit_authenticate_bitwarden_policy() -> Result { let connection = Connection::system().await?; let proxy = AuthorityProxy::new(&connection).await?; - let subject = Subject::new_for_owner(std::process::id(), None, None)?; + + // Use system-bus-name instead of unix-process to avoid PID namespace issues in + // sandboxed environments (e.g., Flatpak). When using unix-process with a PID from + // inside the sandbox, polkit cannot validate it against the host PID namespace. + // + // By using system-bus-name, polkit queries D-Bus for the connection's credentials, + // which includes the correct host PID and UID, avoiding namespace mismatches. + // + // If D-Bus unique name is not available, fall back to the traditional unix-process + // approach for compatibility with non-sandboxed environments. + let subject = if let Some(bus_name) = connection.unique_name() { + use zbus::zvariant::{OwnedValue, Str}; + let mut subject_details = std::collections::HashMap::new(); + subject_details.insert( + "name".to_string(), + OwnedValue::from(Str::from(bus_name.as_str())), + ); + Subject { + subject_kind: "system-bus-name".to_string(), + subject_details, + } + } else { + // Fallback: use unix-process with PID (may not work in sandboxed environments) + Subject::new_for_owner(std::process::id(), None, None)? + }; + let details = std::collections::HashMap::new(); let authorization_result = proxy .check_authorization( diff --git a/apps/desktop/desktop_native/napi/src/autofill.rs b/apps/desktop/desktop_native/napi/src/autofill.rs deleted file mode 100644 index 7717b22ccef..00000000000 --- a/apps/desktop/desktop_native/napi/src/autofill.rs +++ /dev/null @@ -1,332 +0,0 @@ -#[napi] -pub mod autofill { - use desktop_core::ipc::server::{Message, MessageType}; - use napi::{ - bindgen_prelude::FnArgs, - threadsafe_function::{ThreadsafeFunction, ThreadsafeFunctionCallMode}, - }; - use serde::{de::DeserializeOwned, Deserialize, Serialize}; - use tracing::error; - - #[napi] - pub async fn run_command(value: String) -> napi::Result { - desktop_core::autofill::run_command(value) - .await - .map_err(|e| napi::Error::from_reason(e.to_string())) - } - - #[derive(Debug, serde::Serialize, serde:: Deserialize)] - pub enum BitwardenError { - Internal(String), - } - - #[napi(string_enum)] - #[derive(Debug, Serialize, Deserialize)] - #[serde(rename_all = "camelCase")] - pub enum UserVerification { - #[napi(value = "preferred")] - Preferred, - #[napi(value = "required")] - Required, - #[napi(value = "discouraged")] - Discouraged, - } - - #[derive(Serialize, Deserialize)] - #[serde(bound = "T: Serialize + DeserializeOwned")] - pub struct PasskeyMessage { - pub sequence_number: u32, - pub value: Result, - } - - #[napi(object)] - #[derive(Debug, Serialize, Deserialize)] - #[serde(rename_all = "camelCase")] - pub struct Position { - pub x: i32, - pub y: i32, - } - - #[napi(object)] - #[derive(Debug, Serialize, Deserialize)] - #[serde(rename_all = "camelCase")] - pub struct PasskeyRegistrationRequest { - pub rp_id: String, - pub user_name: String, - pub user_handle: Vec, - pub client_data_hash: Vec, - pub user_verification: UserVerification, - pub supported_algorithms: Vec, - pub window_xy: Position, - pub excluded_credentials: Vec>, - } - - #[napi(object)] - #[derive(Serialize, Deserialize)] - #[serde(rename_all = "camelCase")] - pub struct PasskeyRegistrationResponse { - pub rp_id: String, - pub client_data_hash: Vec, - pub credential_id: Vec, - pub attestation_object: Vec, - } - - #[napi(object)] - #[derive(Debug, Serialize, Deserialize)] - #[serde(rename_all = "camelCase")] - pub struct PasskeyAssertionRequest { - pub rp_id: String, - pub client_data_hash: Vec, - pub user_verification: UserVerification, - pub allowed_credentials: Vec>, - pub window_xy: Position, - //extension_input: Vec, TODO: Implement support for extensions - } - - #[napi(object)] - #[derive(Debug, Serialize, Deserialize)] - #[serde(rename_all = "camelCase")] - pub struct PasskeyAssertionWithoutUserInterfaceRequest { - pub rp_id: String, - pub credential_id: Vec, - pub user_name: String, - pub user_handle: Vec, - pub record_identifier: Option, - pub client_data_hash: Vec, - pub user_verification: UserVerification, - pub window_xy: Position, - } - - #[napi(object)] - #[derive(Debug, Serialize, Deserialize)] - #[serde(rename_all = "camelCase")] - pub struct NativeStatus { - pub key: String, - pub value: String, - } - - #[napi(object)] - #[derive(Serialize, Deserialize)] - #[serde(rename_all = "camelCase")] - pub struct PasskeyAssertionResponse { - pub rp_id: String, - pub user_handle: Vec, - pub signature: Vec, - pub client_data_hash: Vec, - pub authenticator_data: Vec, - pub credential_id: Vec, - } - - #[napi] - pub struct AutofillIpcServer { - server: desktop_core::ipc::server::Server, - } - - // FIXME: Remove unwraps! They panic and terminate the whole application. - #[allow(clippy::unwrap_used)] - #[napi] - impl AutofillIpcServer { - /// Create and start the IPC server without blocking. - /// - /// @param name The endpoint name to listen on. This name uniquely identifies the IPC - /// connection and must be the same for both the server and client. @param callback - /// This function will be called whenever a message is received from a client. - #[allow(clippy::unused_async)] // FIXME: Remove unused async! - #[napi(factory)] - pub async fn listen( - name: String, - // Ideally we'd have a single callback that has an enum containing the request values, - // but NAPI doesn't support that just yet - #[napi( - ts_arg_type = "(error: null | Error, clientId: number, sequenceNumber: number, message: PasskeyRegistrationRequest) => void" - )] - registration_callback: ThreadsafeFunction< - FnArgs<(u32, u32, PasskeyRegistrationRequest)>, - >, - #[napi( - ts_arg_type = "(error: null | Error, clientId: number, sequenceNumber: number, message: PasskeyAssertionRequest) => void" - )] - assertion_callback: ThreadsafeFunction< - FnArgs<(u32, u32, PasskeyAssertionRequest)>, - >, - #[napi( - ts_arg_type = "(error: null | Error, clientId: number, sequenceNumber: number, message: PasskeyAssertionWithoutUserInterfaceRequest) => void" - )] - assertion_without_user_interface_callback: ThreadsafeFunction< - FnArgs<(u32, u32, PasskeyAssertionWithoutUserInterfaceRequest)>, - >, - #[napi( - ts_arg_type = "(error: null | Error, clientId: number, sequenceNumber: number, message: NativeStatus) => void" - )] - native_status_callback: ThreadsafeFunction<(u32, u32, NativeStatus)>, - ) -> napi::Result { - let (send, mut recv) = tokio::sync::mpsc::channel::(32); - tokio::spawn(async move { - while let Some(Message { - client_id, - kind, - message, - }) = recv.recv().await - { - match kind { - // TODO: We're ignoring the connection and disconnection messages for now - MessageType::Connected | MessageType::Disconnected => continue, - MessageType::Message => { - let Some(message) = message else { - error!("Message is empty"); - continue; - }; - - match serde_json::from_str::>( - &message, - ) { - Ok(msg) => { - let value = msg - .value - .map(|value| (client_id, msg.sequence_number, value).into()) - .map_err(|e| napi::Error::from_reason(format!("{e:?}"))); - - assertion_callback - .call(value, ThreadsafeFunctionCallMode::NonBlocking); - continue; - } - Err(e) => { - error!(error = %e, "Error deserializing message1"); - } - } - - match serde_json::from_str::< - PasskeyMessage, - >(&message) - { - Ok(msg) => { - let value = msg - .value - .map(|value| (client_id, msg.sequence_number, value).into()) - .map_err(|e| napi::Error::from_reason(format!("{e:?}"))); - - assertion_without_user_interface_callback - .call(value, ThreadsafeFunctionCallMode::NonBlocking); - continue; - } - Err(e) => { - error!(error = %e, "Error deserializing message1"); - } - } - - match serde_json::from_str::>( - &message, - ) { - Ok(msg) => { - let value = msg - .value - .map(|value| (client_id, msg.sequence_number, value).into()) - .map_err(|e| napi::Error::from_reason(format!("{e:?}"))); - registration_callback - .call(value, ThreadsafeFunctionCallMode::NonBlocking); - continue; - } - Err(e) => { - error!(error = %e, "Error deserializing message2"); - } - } - - match serde_json::from_str::>(&message) { - Ok(msg) => { - let value = msg - .value - .map(|value| (client_id, msg.sequence_number, value)) - .map_err(|e| napi::Error::from_reason(format!("{e:?}"))); - native_status_callback - .call(value, ThreadsafeFunctionCallMode::NonBlocking); - continue; - } - Err(error) => { - error!(%error, "Unable to deserialze native status."); - } - } - - error!(message, "Received an unknown message2"); - } - } - } - }); - - let path = desktop_core::ipc::path(&name); - - let server = desktop_core::ipc::server::Server::start(&path, send).map_err(|e| { - napi::Error::from_reason(format!( - "Error listening to server - Path: {path:?} - Error: {e} - {e:?}" - )) - })?; - - Ok(AutofillIpcServer { server }) - } - - /// Return the path to the IPC server. - #[napi] - pub fn get_path(&self) -> String { - self.server.path.to_string_lossy().to_string() - } - - /// Stop the IPC server. - #[napi] - pub fn stop(&self) -> napi::Result<()> { - self.server.stop(); - Ok(()) - } - - #[napi] - pub fn complete_registration( - &self, - client_id: u32, - sequence_number: u32, - response: PasskeyRegistrationResponse, - ) -> napi::Result { - let message = PasskeyMessage { - sequence_number, - value: Ok(response), - }; - self.send(client_id, serde_json::to_string(&message).unwrap()) - } - - #[napi] - pub fn complete_assertion( - &self, - client_id: u32, - sequence_number: u32, - response: PasskeyAssertionResponse, - ) -> napi::Result { - let message = PasskeyMessage { - sequence_number, - value: Ok(response), - }; - self.send(client_id, serde_json::to_string(&message).unwrap()) - } - - #[napi] - pub fn complete_error( - &self, - client_id: u32, - sequence_number: u32, - error: String, - ) -> napi::Result { - let message: PasskeyMessage<()> = PasskeyMessage { - sequence_number, - value: Err(BitwardenError::Internal(error)), - }; - self.send(client_id, serde_json::to_string(&message).unwrap()) - } - - // TODO: Add a way to send a message to a specific client? - fn send(&self, _client_id: u32, message: String) -> napi::Result { - self.server - .send(message) - .map_err(|e| { - napi::Error::from_reason(format!("Error sending message - Error: {e} - {e:?}")) - }) - // NAPI doesn't support u64 or usize, so we need to convert to u32 - .map(|u| u32::try_from(u).unwrap_or_default()) - } - } -} diff --git a/apps/desktop/desktop_native/napi/src/autostart.rs b/apps/desktop/desktop_native/napi/src/autostart.rs deleted file mode 100644 index 3068226809e..00000000000 --- a/apps/desktop/desktop_native/napi/src/autostart.rs +++ /dev/null @@ -1,9 +0,0 @@ -#[napi] -pub mod autostart { - #[napi] - pub async fn set_autostart(autostart: bool, params: Vec) -> napi::Result<()> { - desktop_core::autostart::set_autostart(autostart, params) - .await - .map_err(|e| napi::Error::from_reason(format!("Error setting autostart - {e} - {e:?}"))) - } -} diff --git a/apps/desktop/desktop_native/napi/src/autotype.rs b/apps/desktop/desktop_native/napi/src/autotype.rs deleted file mode 100644 index b63c95ceb5c..00000000000 --- a/apps/desktop/desktop_native/napi/src/autotype.rs +++ /dev/null @@ -1,20 +0,0 @@ -#[napi] -pub mod autotype { - #[napi] - pub fn get_foreground_window_title() -> napi::Result { - autotype::get_foreground_window_title().map_err(|_| { - napi::Error::from_reason( - "Autotype Error: failed to get foreground window title".to_string(), - ) - }) - } - - #[napi] - pub fn type_input( - input: Vec, - keyboard_shortcut: Vec, - ) -> napi::Result<(), napi::Status> { - autotype::type_input(&input, &keyboard_shortcut) - .map_err(|e| napi::Error::from_reason(format!("Autotype Error: {e}"))) - } -} diff --git a/apps/desktop/desktop_native/napi/src/biometrics.rs b/apps/desktop/desktop_native/napi/src/biometrics.rs deleted file mode 100644 index bca802d5884..00000000000 --- a/apps/desktop/desktop_native/napi/src/biometrics.rs +++ /dev/null @@ -1,100 +0,0 @@ -#[napi] -pub mod biometrics { - use desktop_core::biometric::{Biometric, BiometricTrait}; - - // Prompt for biometric confirmation - #[napi] - pub async fn prompt( - hwnd: napi::bindgen_prelude::Buffer, - message: String, - ) -> napi::Result { - Biometric::prompt(hwnd.into(), message) - .await - .map_err(|e| napi::Error::from_reason(e.to_string())) - } - - #[napi] - pub async fn available() -> napi::Result { - Biometric::available() - .await - .map_err(|e| napi::Error::from_reason(e.to_string())) - } - - #[napi] - pub async fn set_biometric_secret( - service: String, - account: String, - secret: String, - key_material: Option, - iv_b64: String, - ) -> napi::Result { - Biometric::set_biometric_secret( - &service, - &account, - &secret, - key_material.map(|m| m.into()), - &iv_b64, - ) - .await - .map_err(|e| napi::Error::from_reason(e.to_string())) - } - - /// Retrieves the biometric secret for the given service and account. - /// Throws Error with message [`passwords::PASSWORD_NOT_FOUND`] if the secret does not exist. - #[napi] - pub async fn get_biometric_secret( - service: String, - account: String, - key_material: Option, - ) -> napi::Result { - Biometric::get_biometric_secret(&service, &account, key_material.map(|m| m.into())) - .await - .map_err(|e| napi::Error::from_reason(e.to_string())) - } - - /// Derives key material from biometric data. Returns a string encoded with a - /// base64 encoded key and the base64 encoded challenge used to create it - /// separated by a `|` character. - /// - /// If the iv is provided, it will be used as the challenge. Otherwise a random challenge will - /// be generated. - /// - /// `format!("|")` - #[allow(clippy::unused_async)] // FIXME: Remove unused async! - #[napi] - pub async fn derive_key_material(iv: Option) -> napi::Result { - Biometric::derive_key_material(iv.as_deref()) - .map(|k| k.into()) - .map_err(|e| napi::Error::from_reason(e.to_string())) - } - - #[napi(object)] - pub struct KeyMaterial { - pub os_key_part_b64: String, - pub client_key_part_b64: Option, - } - - impl From for desktop_core::biometric::KeyMaterial { - fn from(km: KeyMaterial) -> Self { - desktop_core::biometric::KeyMaterial { - os_key_part_b64: km.os_key_part_b64, - client_key_part_b64: km.client_key_part_b64, - } - } - } - - #[napi(object)] - pub struct OsDerivedKey { - pub key_b64: String, - pub iv_b64: String, - } - - impl From for OsDerivedKey { - fn from(km: desktop_core::biometric::OsDerivedKey) -> Self { - OsDerivedKey { - key_b64: km.key_b64, - iv_b64: km.iv_b64, - } - } - } -} diff --git a/apps/desktop/desktop_native/napi/src/biometrics_v2.rs b/apps/desktop/desktop_native/napi/src/biometrics_v2.rs deleted file mode 100644 index 2df3a6a07be..00000000000 --- a/apps/desktop/desktop_native/napi/src/biometrics_v2.rs +++ /dev/null @@ -1,116 +0,0 @@ -#[napi] -pub mod biometrics_v2 { - use desktop_core::biometric_v2::BiometricTrait; - - #[napi] - pub struct BiometricLockSystem { - inner: desktop_core::biometric_v2::BiometricLockSystem, - } - - #[napi] - pub fn init_biometric_system() -> napi::Result { - Ok(BiometricLockSystem { - inner: desktop_core::biometric_v2::BiometricLockSystem::new(), - }) - } - - #[napi] - pub async fn authenticate( - biometric_lock_system: &BiometricLockSystem, - hwnd: napi::bindgen_prelude::Buffer, - message: String, - ) -> napi::Result { - biometric_lock_system - .inner - .authenticate(hwnd.into(), message) - .await - .map_err(|e| napi::Error::from_reason(e.to_string())) - } - - #[napi] - pub async fn authenticate_available( - biometric_lock_system: &BiometricLockSystem, - ) -> napi::Result { - biometric_lock_system - .inner - .authenticate_available() - .await - .map_err(|e| napi::Error::from_reason(e.to_string())) - } - - #[napi] - pub async fn enroll_persistent( - biometric_lock_system: &BiometricLockSystem, - user_id: String, - key: napi::bindgen_prelude::Buffer, - ) -> napi::Result<()> { - biometric_lock_system - .inner - .enroll_persistent(&user_id, &key) - .await - .map_err(|e| napi::Error::from_reason(e.to_string())) - } - - #[napi] - pub async fn provide_key( - biometric_lock_system: &BiometricLockSystem, - user_id: String, - key: napi::bindgen_prelude::Buffer, - ) -> napi::Result<()> { - biometric_lock_system - .inner - .provide_key(&user_id, &key) - .await; - Ok(()) - } - - #[napi] - pub async fn unlock( - biometric_lock_system: &BiometricLockSystem, - user_id: String, - hwnd: napi::bindgen_prelude::Buffer, - ) -> napi::Result { - biometric_lock_system - .inner - .unlock(&user_id, hwnd.into()) - .await - .map_err(|e| napi::Error::from_reason(e.to_string())) - .map(|v| v.into()) - } - - #[napi] - pub async fn unlock_available( - biometric_lock_system: &BiometricLockSystem, - user_id: String, - ) -> napi::Result { - biometric_lock_system - .inner - .unlock_available(&user_id) - .await - .map_err(|e| napi::Error::from_reason(e.to_string())) - } - - #[napi] - pub async fn has_persistent( - biometric_lock_system: &BiometricLockSystem, - user_id: String, - ) -> napi::Result { - biometric_lock_system - .inner - .has_persistent(&user_id) - .await - .map_err(|e| napi::Error::from_reason(e.to_string())) - } - - #[napi] - pub async fn unenroll( - biometric_lock_system: &BiometricLockSystem, - user_id: String, - ) -> napi::Result<()> { - biometric_lock_system - .inner - .unenroll(&user_id) - .await - .map_err(|e| napi::Error::from_reason(e.to_string())) - } -} diff --git a/apps/desktop/desktop_native/napi/src/chromium_importer.rs b/apps/desktop/desktop_native/napi/src/chromium_importer.rs deleted file mode 100644 index da295984a47..00000000000 --- a/apps/desktop/desktop_native/napi/src/chromium_importer.rs +++ /dev/null @@ -1,116 +0,0 @@ -#[napi] -pub mod chromium_importer { - use std::collections::HashMap; - - use chromium_importer::{ - chromium::{ - DefaultInstalledBrowserRetriever, LoginImportResult as _LoginImportResult, - ProfileInfo as _ProfileInfo, - }, - metadata::NativeImporterMetadata as _NativeImporterMetadata, - }; - - #[napi(object)] - pub struct ProfileInfo { - pub id: String, - pub name: String, - } - - #[napi(object)] - pub struct Login { - pub url: String, - pub username: String, - pub password: String, - pub note: String, - } - - #[napi(object)] - pub struct LoginImportFailure { - pub url: String, - pub username: String, - pub error: String, - } - - #[napi(object)] - pub struct LoginImportResult { - pub login: Option, - pub failure: Option, - } - - #[napi(object)] - pub struct NativeImporterMetadata { - pub id: String, - pub loaders: Vec, - pub instructions: String, - } - - impl From<_LoginImportResult> for LoginImportResult { - fn from(l: _LoginImportResult) -> Self { - match l { - _LoginImportResult::Success(l) => LoginImportResult { - login: Some(Login { - url: l.url, - username: l.username, - password: l.password, - note: l.note, - }), - failure: None, - }, - _LoginImportResult::Failure(l) => LoginImportResult { - login: None, - failure: Some(LoginImportFailure { - url: l.url, - username: l.username, - error: l.error, - }), - }, - } - } - } - - impl From<_ProfileInfo> for ProfileInfo { - fn from(p: _ProfileInfo) -> Self { - ProfileInfo { - id: p.folder, - name: p.name, - } - } - } - - impl From<_NativeImporterMetadata> for NativeImporterMetadata { - fn from(m: _NativeImporterMetadata) -> Self { - NativeImporterMetadata { - id: m.id, - loaders: m.loaders, - instructions: m.instructions, - } - } - } - - #[napi] - /// Returns OS aware metadata describing supported Chromium based importers as a JSON string. - pub fn get_metadata() -> HashMap { - chromium_importer::metadata::get_supported_importers::() - .into_iter() - .map(|(browser, metadata)| (browser, NativeImporterMetadata::from(metadata))) - .collect() - } - - #[napi] - pub fn get_available_profiles(browser: String) -> napi::Result> { - chromium_importer::chromium::get_available_profiles(&browser) - .map(|profiles| profiles.into_iter().map(ProfileInfo::from).collect()) - .map_err(|e| napi::Error::from_reason(e.to_string())) - } - - #[napi] - pub async fn import_logins( - browser: String, - profile_id: String, - ) -> napi::Result> { - chromium_importer::chromium::import_logins(&browser, &profile_id) - .await - .map(|logins| logins.into_iter().map(LoginImportResult::from).collect()) - .map_err(|e| napi::Error::from_reason(e.to_string())) - } -} diff --git a/apps/desktop/desktop_native/napi/src/clipboards.rs b/apps/desktop/desktop_native/napi/src/clipboards.rs deleted file mode 100644 index 810e457dd60..00000000000 --- a/apps/desktop/desktop_native/napi/src/clipboards.rs +++ /dev/null @@ -1,15 +0,0 @@ -#[napi] -pub mod clipboards { - #[allow(clippy::unused_async)] // FIXME: Remove unused async! - #[napi] - pub async fn read() -> napi::Result { - desktop_core::clipboard::read().map_err(|e| napi::Error::from_reason(e.to_string())) - } - - #[allow(clippy::unused_async)] // FIXME: Remove unused async! - #[napi] - pub async fn write(text: String, password: bool) -> napi::Result<()> { - desktop_core::clipboard::write(&text, password) - .map_err(|e| napi::Error::from_reason(e.to_string())) - } -} diff --git a/apps/desktop/desktop_native/napi/src/ipc.rs b/apps/desktop/desktop_native/napi/src/ipc.rs deleted file mode 100644 index ba72b1dce2b..00000000000 --- a/apps/desktop/desktop_native/napi/src/ipc.rs +++ /dev/null @@ -1,106 +0,0 @@ -#[napi] -pub mod ipc { - use desktop_core::ipc::server::{Message, MessageType}; - use napi::threadsafe_function::{ThreadsafeFunction, ThreadsafeFunctionCallMode}; - - #[napi(object)] - pub struct IpcMessage { - pub client_id: u32, - pub kind: IpcMessageType, - pub message: Option, - } - - impl From for IpcMessage { - fn from(message: Message) -> Self { - IpcMessage { - client_id: message.client_id, - kind: message.kind.into(), - message: message.message, - } - } - } - - #[napi] - pub enum IpcMessageType { - Connected, - Disconnected, - Message, - } - - impl From for IpcMessageType { - fn from(message_type: MessageType) -> Self { - match message_type { - MessageType::Connected => IpcMessageType::Connected, - MessageType::Disconnected => IpcMessageType::Disconnected, - MessageType::Message => IpcMessageType::Message, - } - } - } - - #[napi] - pub struct NativeIpcServer { - server: desktop_core::ipc::server::Server, - } - - #[napi] - impl NativeIpcServer { - /// Create and start the IPC server without blocking. - /// - /// @param name The endpoint name to listen on. This name uniquely identifies the IPC - /// connection and must be the same for both the server and client. @param callback - /// This function will be called whenever a message is received from a client. - #[allow(clippy::unused_async)] // FIXME: Remove unused async! - #[napi(factory)] - pub async fn listen( - name: String, - #[napi(ts_arg_type = "(error: null | Error, message: IpcMessage) => void")] - callback: ThreadsafeFunction, - ) -> napi::Result { - let (send, mut recv) = tokio::sync::mpsc::channel::(32); - tokio::spawn(async move { - while let Some(message) = recv.recv().await { - callback.call(Ok(message.into()), ThreadsafeFunctionCallMode::NonBlocking); - } - }); - - let path = desktop_core::ipc::path(&name); - - let server = desktop_core::ipc::server::Server::start(&path, send).map_err(|e| { - napi::Error::from_reason(format!( - "Error listening to server - Path: {path:?} - Error: {e} - {e:?}" - )) - })?; - - Ok(NativeIpcServer { server }) - } - - /// Return the path to the IPC server. - #[napi] - pub fn get_path(&self) -> String { - self.server.path.to_string_lossy().to_string() - } - - /// Stop the IPC server. - #[napi] - pub fn stop(&self) -> napi::Result<()> { - self.server.stop(); - Ok(()) - } - - /// Send a message over the IPC server to all the connected clients - /// - /// @return The number of clients that the message was sent to. Note that the number of - /// messages actually received may be less, as some clients could disconnect before - /// receiving the message. - #[napi] - pub fn send(&self, message: String) -> napi::Result { - self.server - .send(message) - .map_err(|e| { - napi::Error::from_reason(format!("Error sending message - Error: {e} - {e:?}")) - }) - // NAPI doesn't support u64 or usize, so we need to convert to u32 - .map(|u| u32::try_from(u).unwrap_or_default()) - } - } -} diff --git a/apps/desktop/desktop_native/napi/src/lib.rs b/apps/desktop/desktop_native/napi/src/lib.rs index e3abfd50e7a..588f757631c 100644 --- a/apps/desktop/desktop_native/napi/src/lib.rs +++ b/apps/desktop/desktop_native/napi/src/lib.rs @@ -4,22 +4,1244 @@ extern crate napi_derive; mod passkey_authenticator_internal; mod registry; -// NAPI namespaces -// In each of these modules, the types are defined within a nested namespace of -// the same name so that NAPI can export the TypeScript types within a -// namespace. -pub mod autofill; -pub mod autostart; -pub mod autotype; -pub mod biometrics; -pub mod biometrics_v2; -pub mod chromium_importer; -pub mod clipboards; -pub mod ipc; -pub mod logging; -pub mod passkey_authenticator; -pub mod passwords; -pub mod powermonitors; -pub mod processisolations; -pub mod sshagent; -pub mod windows_registry; +#[napi] +pub mod passwords { + /// The error message returned when a password is not found during retrieval or deletion. + #[napi] + pub const PASSWORD_NOT_FOUND: &str = desktop_core::password::PASSWORD_NOT_FOUND; + + /// Fetch the stored password from the keychain. + /// Throws {@link Error} with message {@link PASSWORD_NOT_FOUND} if the password does not exist. + #[napi] + pub async fn get_password(service: String, account: String) -> napi::Result { + desktop_core::password::get_password(&service, &account) + .await + .map_err(|e| napi::Error::from_reason(e.to_string())) + } + + /// Save the password to the keychain. Adds an entry if none exists otherwise updates the + /// existing entry. + #[napi] + pub async fn set_password( + service: String, + account: String, + password: String, + ) -> napi::Result<()> { + desktop_core::password::set_password(&service, &account, &password) + .await + .map_err(|e| napi::Error::from_reason(e.to_string())) + } + + /// Delete the stored password from the keychain. + /// Throws {@link Error} with message {@link PASSWORD_NOT_FOUND} if the password does not exist. + #[napi] + pub async fn delete_password(service: String, account: String) -> napi::Result<()> { + desktop_core::password::delete_password(&service, &account) + .await + .map_err(|e| napi::Error::from_reason(e.to_string())) + } + + /// Checks if the os secure storage is available + #[napi] + pub async fn is_available() -> napi::Result { + desktop_core::password::is_available() + .await + .map_err(|e| napi::Error::from_reason(e.to_string())) + } +} + +#[napi] +pub mod biometrics { + use desktop_core::biometric::{Biometric, BiometricTrait}; + + // Prompt for biometric confirmation + #[napi] + pub async fn prompt( + hwnd: napi::bindgen_prelude::Buffer, + message: String, + ) -> napi::Result { + Biometric::prompt(hwnd.into(), message) + .await + .map_err(|e| napi::Error::from_reason(e.to_string())) + } + + #[napi] + pub async fn available() -> napi::Result { + Biometric::available() + .await + .map_err(|e| napi::Error::from_reason(e.to_string())) + } + + #[napi] + pub async fn set_biometric_secret( + service: String, + account: String, + secret: String, + key_material: Option, + iv_b64: String, + ) -> napi::Result { + Biometric::set_biometric_secret( + &service, + &account, + &secret, + key_material.map(|m| m.into()), + &iv_b64, + ) + .await + .map_err(|e| napi::Error::from_reason(e.to_string())) + } + + /// Retrieves the biometric secret for the given service and account. + /// Throws Error with message [`passwords::PASSWORD_NOT_FOUND`] if the secret does not exist. + #[napi] + pub async fn get_biometric_secret( + service: String, + account: String, + key_material: Option, + ) -> napi::Result { + Biometric::get_biometric_secret(&service, &account, key_material.map(|m| m.into())) + .await + .map_err(|e| napi::Error::from_reason(e.to_string())) + } + + /// Derives key material from biometric data. Returns a string encoded with a + /// base64 encoded key and the base64 encoded challenge used to create it + /// separated by a `|` character. + /// + /// If the iv is provided, it will be used as the challenge. Otherwise a random challenge will + /// be generated. + /// + /// `format!("|")` + #[allow(clippy::unused_async)] // FIXME: Remove unused async! + #[napi] + pub async fn derive_key_material(iv: Option) -> napi::Result { + Biometric::derive_key_material(iv.as_deref()) + .map(|k| k.into()) + .map_err(|e| napi::Error::from_reason(e.to_string())) + } + + #[napi(object)] + pub struct KeyMaterial { + pub os_key_part_b64: String, + pub client_key_part_b64: Option, + } + + impl From for desktop_core::biometric::KeyMaterial { + fn from(km: KeyMaterial) -> Self { + desktop_core::biometric::KeyMaterial { + os_key_part_b64: km.os_key_part_b64, + client_key_part_b64: km.client_key_part_b64, + } + } + } + + #[napi(object)] + pub struct OsDerivedKey { + pub key_b64: String, + pub iv_b64: String, + } + + impl From for OsDerivedKey { + fn from(km: desktop_core::biometric::OsDerivedKey) -> Self { + OsDerivedKey { + key_b64: km.key_b64, + iv_b64: km.iv_b64, + } + } + } +} + +#[napi] +pub mod biometrics_v2 { + use desktop_core::biometric_v2::BiometricTrait; + + #[napi] + pub struct BiometricLockSystem { + inner: desktop_core::biometric_v2::BiometricLockSystem, + } + + #[napi] + pub fn init_biometric_system() -> napi::Result { + Ok(BiometricLockSystem { + inner: desktop_core::biometric_v2::BiometricLockSystem::new(), + }) + } + + #[napi] + pub async fn authenticate( + biometric_lock_system: &BiometricLockSystem, + hwnd: napi::bindgen_prelude::Buffer, + message: String, + ) -> napi::Result { + biometric_lock_system + .inner + .authenticate(hwnd.into(), message) + .await + .map_err(|e| napi::Error::from_reason(e.to_string())) + } + + #[napi] + pub async fn authenticate_available( + biometric_lock_system: &BiometricLockSystem, + ) -> napi::Result { + biometric_lock_system + .inner + .authenticate_available() + .await + .map_err(|e| napi::Error::from_reason(e.to_string())) + } + + #[napi] + pub async fn enroll_persistent( + biometric_lock_system: &BiometricLockSystem, + user_id: String, + key: napi::bindgen_prelude::Buffer, + ) -> napi::Result<()> { + biometric_lock_system + .inner + .enroll_persistent(&user_id, &key) + .await + .map_err(|e| napi::Error::from_reason(e.to_string())) + } + + #[napi] + pub async fn provide_key( + biometric_lock_system: &BiometricLockSystem, + user_id: String, + key: napi::bindgen_prelude::Buffer, + ) -> napi::Result<()> { + biometric_lock_system + .inner + .provide_key(&user_id, &key) + .await; + Ok(()) + } + + #[napi] + pub async fn unlock( + biometric_lock_system: &BiometricLockSystem, + user_id: String, + hwnd: napi::bindgen_prelude::Buffer, + ) -> napi::Result { + biometric_lock_system + .inner + .unlock(&user_id, hwnd.into()) + .await + .map_err(|e| napi::Error::from_reason(e.to_string())) + .map(|v| v.into()) + } + + #[napi] + pub async fn unlock_available( + biometric_lock_system: &BiometricLockSystem, + user_id: String, + ) -> napi::Result { + biometric_lock_system + .inner + .unlock_available(&user_id) + .await + .map_err(|e| napi::Error::from_reason(e.to_string())) + } + + #[napi] + pub async fn has_persistent( + biometric_lock_system: &BiometricLockSystem, + user_id: String, + ) -> napi::Result { + biometric_lock_system + .inner + .has_persistent(&user_id) + .await + .map_err(|e| napi::Error::from_reason(e.to_string())) + } + + #[napi] + pub async fn unenroll( + biometric_lock_system: &BiometricLockSystem, + user_id: String, + ) -> napi::Result<()> { + biometric_lock_system + .inner + .unenroll(&user_id) + .await + .map_err(|e| napi::Error::from_reason(e.to_string())) + } +} + +#[napi] +pub mod clipboards { + #[allow(clippy::unused_async)] // FIXME: Remove unused async! + #[napi] + pub async fn read() -> napi::Result { + desktop_core::clipboard::read().map_err(|e| napi::Error::from_reason(e.to_string())) + } + + #[allow(clippy::unused_async)] // FIXME: Remove unused async! + #[napi] + pub async fn write(text: String, password: bool) -> napi::Result<()> { + desktop_core::clipboard::write(&text, password) + .map_err(|e| napi::Error::from_reason(e.to_string())) + } +} + +#[napi] +pub mod sshagent { + use std::sync::Arc; + + use napi::{ + bindgen_prelude::Promise, + threadsafe_function::{ThreadsafeFunction, ThreadsafeFunctionCallMode}, + }; + use tokio::{self, sync::Mutex}; + use tracing::error; + + #[napi] + pub struct SshAgentState { + state: desktop_core::ssh_agent::BitwardenDesktopAgent, + } + + #[napi(object)] + pub struct PrivateKey { + pub private_key: String, + pub name: String, + pub cipher_id: String, + } + + #[napi(object)] + pub struct SshKey { + pub private_key: String, + pub public_key: String, + pub key_fingerprint: String, + } + + #[napi(object)] + pub struct SshUIRequest { + pub cipher_id: Option, + pub is_list: bool, + pub process_name: String, + pub is_forwarding: bool, + pub namespace: Option, + } + + #[allow(clippy::unused_async)] // FIXME: Remove unused async! + #[napi] + pub async fn serve( + callback: ThreadsafeFunction>, + ) -> napi::Result { + let (auth_request_tx, mut auth_request_rx) = + tokio::sync::mpsc::channel::(32); + let (auth_response_tx, auth_response_rx) = + tokio::sync::broadcast::channel::<(u32, bool)>(32); + let auth_response_tx_arc = Arc::new(Mutex::new(auth_response_tx)); + // Wrap callback in Arc so it can be shared across spawned tasks + let callback = Arc::new(callback); + tokio::spawn(async move { + let _ = auth_response_rx; + + while let Some(request) = auth_request_rx.recv().await { + let cloned_response_tx_arc = auth_response_tx_arc.clone(); + let cloned_callback = callback.clone(); + tokio::spawn(async move { + let auth_response_tx_arc = cloned_response_tx_arc; + let callback = cloned_callback; + // In NAPI v3, obtain the JS callback return as a Promise and await it + // in Rust + let (tx, rx) = std::sync::mpsc::channel::>(); + let status = callback.call_with_return_value( + Ok(SshUIRequest { + cipher_id: request.cipher_id, + is_list: request.is_list, + process_name: request.process_name, + is_forwarding: request.is_forwarding, + namespace: request.namespace, + }), + ThreadsafeFunctionCallMode::Blocking, + move |ret: Result, napi::Error>, _env| { + if let Ok(p) = ret { + let _ = tx.send(p); + } + Ok(()) + }, + ); + + let result = if status == napi::Status::Ok { + match rx.recv() { + Ok(promise) => match promise.await { + Ok(v) => v, + Err(e) => { + error!(error = %e, "UI callback promise rejected"); + false + } + }, + Err(e) => { + error!(error = %e, "Failed to receive UI callback promise"); + false + } + } + } else { + error!(error = ?status, "Calling UI callback failed"); + false + }; + + let _ = auth_response_tx_arc + .lock() + .await + .send((request.request_id, result)) + .expect("should be able to send auth response to agent"); + }); + } + }); + + match desktop_core::ssh_agent::BitwardenDesktopAgent::start_server( + auth_request_tx, + Arc::new(Mutex::new(auth_response_rx)), + ) { + Ok(state) => Ok(SshAgentState { state }), + Err(e) => Err(napi::Error::from_reason(e.to_string())), + } + } + + #[napi] + pub fn stop(agent_state: &mut SshAgentState) -> napi::Result<()> { + let bitwarden_agent_state = &mut agent_state.state; + bitwarden_agent_state.stop(); + Ok(()) + } + + #[napi] + pub fn is_running(agent_state: &mut SshAgentState) -> bool { + let bitwarden_agent_state = agent_state.state.clone(); + bitwarden_agent_state.is_running() + } + + #[napi] + pub fn set_keys( + agent_state: &mut SshAgentState, + new_keys: Vec, + ) -> napi::Result<()> { + let bitwarden_agent_state = &mut agent_state.state; + bitwarden_agent_state + .set_keys( + new_keys + .iter() + .map(|k| (k.private_key.clone(), k.name.clone(), k.cipher_id.clone())) + .collect(), + ) + .map_err(|e| napi::Error::from_reason(e.to_string()))?; + Ok(()) + } + + #[napi] + pub fn lock(agent_state: &mut SshAgentState) -> napi::Result<()> { + let bitwarden_agent_state = &mut agent_state.state; + bitwarden_agent_state + .lock() + .map_err(|e| napi::Error::from_reason(e.to_string())) + } + + #[napi] + pub fn clear_keys(agent_state: &mut SshAgentState) -> napi::Result<()> { + let bitwarden_agent_state = &mut agent_state.state; + bitwarden_agent_state + .clear_keys() + .map_err(|e| napi::Error::from_reason(e.to_string())) + } +} + +#[napi] +pub mod processisolations { + #[allow(clippy::unused_async)] // FIXME: Remove unused async! + #[napi] + pub async fn disable_coredumps() -> napi::Result<()> { + desktop_core::process_isolation::disable_coredumps() + .map_err(|e| napi::Error::from_reason(e.to_string())) + } + + #[allow(clippy::unused_async)] // FIXME: Remove unused async! + #[napi] + pub async fn is_core_dumping_disabled() -> napi::Result { + desktop_core::process_isolation::is_core_dumping_disabled() + .map_err(|e| napi::Error::from_reason(e.to_string())) + } + + #[allow(clippy::unused_async)] // FIXME: Remove unused async! + #[napi] + pub async fn isolate_process() -> napi::Result<()> { + desktop_core::process_isolation::isolate_process() + .map_err(|e| napi::Error::from_reason(e.to_string())) + } +} + +#[napi] +pub mod powermonitors { + use napi::{ + threadsafe_function::{ThreadsafeFunction, ThreadsafeFunctionCallMode}, + tokio, + }; + + #[napi] + pub async fn on_lock(callback: ThreadsafeFunction<()>) -> napi::Result<()> { + let (tx, mut rx) = tokio::sync::mpsc::channel::<()>(32); + desktop_core::powermonitor::on_lock(tx) + .await + .map_err(|e| napi::Error::from_reason(e.to_string()))?; + tokio::spawn(async move { + while let Some(()) = rx.recv().await { + callback.call(Ok(()), ThreadsafeFunctionCallMode::NonBlocking); + } + }); + Ok(()) + } + + #[napi] + pub async fn is_lock_monitor_available() -> napi::Result { + Ok(desktop_core::powermonitor::is_lock_monitor_available().await) + } +} + +#[napi] +pub mod windows_registry { + #[allow(clippy::unused_async)] // FIXME: Remove unused async! + #[napi] + pub async fn create_key(key: String, subkey: String, value: String) -> napi::Result<()> { + crate::registry::create_key(&key, &subkey, &value) + .map_err(|e| napi::Error::from_reason(e.to_string())) + } + + #[allow(clippy::unused_async)] // FIXME: Remove unused async! + #[napi] + pub async fn delete_key(key: String, subkey: String) -> napi::Result<()> { + crate::registry::delete_key(&key, &subkey) + .map_err(|e| napi::Error::from_reason(e.to_string())) + } +} + +#[napi] +pub mod ipc { + use desktop_core::ipc::server::{Message, MessageType}; + use napi::threadsafe_function::{ThreadsafeFunction, ThreadsafeFunctionCallMode}; + + #[napi(object)] + pub struct IpcMessage { + pub client_id: u32, + pub kind: IpcMessageType, + pub message: Option, + } + + impl From for IpcMessage { + fn from(message: Message) -> Self { + IpcMessage { + client_id: message.client_id, + kind: message.kind.into(), + message: message.message, + } + } + } + + #[napi] + pub enum IpcMessageType { + Connected, + Disconnected, + Message, + } + + impl From for IpcMessageType { + fn from(message_type: MessageType) -> Self { + match message_type { + MessageType::Connected => IpcMessageType::Connected, + MessageType::Disconnected => IpcMessageType::Disconnected, + MessageType::Message => IpcMessageType::Message, + } + } + } + + #[napi] + pub struct NativeIpcServer { + server: desktop_core::ipc::server::Server, + } + + #[napi] + impl NativeIpcServer { + /// Create and start the IPC server without blocking. + /// + /// @param name The endpoint name to listen on. This name uniquely identifies the IPC + /// connection and must be the same for both the server and client. @param callback + /// This function will be called whenever a message is received from a client. + #[allow(clippy::unused_async)] // FIXME: Remove unused async! + #[napi(factory)] + pub async fn listen( + name: String, + #[napi(ts_arg_type = "(error: null | Error, message: IpcMessage) => void")] + callback: ThreadsafeFunction, + ) -> napi::Result { + let (send, mut recv) = tokio::sync::mpsc::channel::(32); + tokio::spawn(async move { + while let Some(message) = recv.recv().await { + callback.call(Ok(message.into()), ThreadsafeFunctionCallMode::NonBlocking); + } + }); + + let path = desktop_core::ipc::path(&name); + + let server = desktop_core::ipc::server::Server::start(&path, send).map_err(|e| { + napi::Error::from_reason(format!( + "Error listening to server - Path: {path:?} - Error: {e} - {e:?}" + )) + })?; + + Ok(NativeIpcServer { server }) + } + + /// Return the path to the IPC server. + #[napi] + pub fn get_path(&self) -> String { + self.server.path.to_string_lossy().to_string() + } + + /// Stop the IPC server. + #[napi] + pub fn stop(&self) -> napi::Result<()> { + self.server.stop(); + Ok(()) + } + + /// Send a message over the IPC server to all the connected clients + /// + /// @return The number of clients that the message was sent to. Note that the number of + /// messages actually received may be less, as some clients could disconnect before + /// receiving the message. + #[napi] + pub fn send(&self, message: String) -> napi::Result { + self.server + .send(message) + .map_err(|e| { + napi::Error::from_reason(format!("Error sending message - Error: {e} - {e:?}")) + }) + // NAPI doesn't support u64 or usize, so we need to convert to u32 + .map(|u| u32::try_from(u).unwrap_or_default()) + } + } +} + +#[napi] +pub mod autostart { + #[napi] + pub async fn set_autostart(autostart: bool, params: Vec) -> napi::Result<()> { + desktop_core::autostart::set_autostart(autostart, params) + .await + .map_err(|e| napi::Error::from_reason(format!("Error setting autostart - {e} - {e:?}"))) + } +} + +#[napi] +pub mod autofill { + use desktop_core::ipc::server::{Message, MessageType}; + use napi::{ + bindgen_prelude::FnArgs, + threadsafe_function::{ThreadsafeFunction, ThreadsafeFunctionCallMode}, + }; + use serde::{de::DeserializeOwned, Deserialize, Serialize}; + use tracing::error; + + #[napi] + pub async fn run_command(value: String) -> napi::Result { + desktop_core::autofill::run_command(value) + .await + .map_err(|e| napi::Error::from_reason(e.to_string())) + } + + #[derive(Debug, serde::Serialize, serde:: Deserialize)] + pub enum BitwardenError { + Internal(String), + } + + #[napi(string_enum)] + #[derive(Debug, Serialize, Deserialize)] + #[serde(rename_all = "camelCase")] + pub enum UserVerification { + #[napi(value = "preferred")] + Preferred, + #[napi(value = "required")] + Required, + #[napi(value = "discouraged")] + Discouraged, + } + + #[derive(Serialize, Deserialize)] + #[serde(bound = "T: Serialize + DeserializeOwned")] + pub struct PasskeyMessage { + pub sequence_number: u32, + pub value: Result, + } + + #[napi(object)] + #[derive(Debug, Serialize, Deserialize)] + #[serde(rename_all = "camelCase")] + pub struct Position { + pub x: i32, + pub y: i32, + } + + #[napi(object)] + #[derive(Debug, Serialize, Deserialize)] + #[serde(rename_all = "camelCase")] + pub struct PasskeyRegistrationRequest { + pub rp_id: String, + pub user_name: String, + pub user_handle: Vec, + pub client_data_hash: Vec, + pub user_verification: UserVerification, + pub supported_algorithms: Vec, + pub window_xy: Position, + pub excluded_credentials: Vec>, + } + + #[napi(object)] + #[derive(Serialize, Deserialize)] + #[serde(rename_all = "camelCase")] + pub struct PasskeyRegistrationResponse { + pub rp_id: String, + pub client_data_hash: Vec, + pub credential_id: Vec, + pub attestation_object: Vec, + } + + #[napi(object)] + #[derive(Debug, Serialize, Deserialize)] + #[serde(rename_all = "camelCase")] + pub struct PasskeyAssertionRequest { + pub rp_id: String, + pub client_data_hash: Vec, + pub user_verification: UserVerification, + pub allowed_credentials: Vec>, + pub window_xy: Position, + //extension_input: Vec, TODO: Implement support for extensions + } + + #[napi(object)] + #[derive(Debug, Serialize, Deserialize)] + #[serde(rename_all = "camelCase")] + pub struct PasskeyAssertionWithoutUserInterfaceRequest { + pub rp_id: String, + pub credential_id: Vec, + pub user_name: String, + pub user_handle: Vec, + pub record_identifier: Option, + pub client_data_hash: Vec, + pub user_verification: UserVerification, + pub window_xy: Position, + } + + #[napi(object)] + #[derive(Debug, Serialize, Deserialize)] + #[serde(rename_all = "camelCase")] + pub struct NativeStatus { + pub key: String, + pub value: String, + } + + #[napi(object)] + #[derive(Serialize, Deserialize)] + #[serde(rename_all = "camelCase")] + pub struct PasskeyAssertionResponse { + pub rp_id: String, + pub user_handle: Vec, + pub signature: Vec, + pub client_data_hash: Vec, + pub authenticator_data: Vec, + pub credential_id: Vec, + } + + #[napi] + pub struct AutofillIpcServer { + server: desktop_core::ipc::server::Server, + } + + // FIXME: Remove unwraps! They panic and terminate the whole application. + #[allow(clippy::unwrap_used)] + #[napi] + impl AutofillIpcServer { + /// Create and start the IPC server without blocking. + /// + /// @param name The endpoint name to listen on. This name uniquely identifies the IPC + /// connection and must be the same for both the server and client. @param callback + /// This function will be called whenever a message is received from a client. + #[allow(clippy::unused_async)] // FIXME: Remove unused async! + #[napi(factory)] + pub async fn listen( + name: String, + // Ideally we'd have a single callback that has an enum containing the request values, + // but NAPI doesn't support that just yet + #[napi( + ts_arg_type = "(error: null | Error, clientId: number, sequenceNumber: number, message: PasskeyRegistrationRequest) => void" + )] + registration_callback: ThreadsafeFunction< + FnArgs<(u32, u32, PasskeyRegistrationRequest)>, + >, + #[napi( + ts_arg_type = "(error: null | Error, clientId: number, sequenceNumber: number, message: PasskeyAssertionRequest) => void" + )] + assertion_callback: ThreadsafeFunction< + FnArgs<(u32, u32, PasskeyAssertionRequest)>, + >, + #[napi( + ts_arg_type = "(error: null | Error, clientId: number, sequenceNumber: number, message: PasskeyAssertionWithoutUserInterfaceRequest) => void" + )] + assertion_without_user_interface_callback: ThreadsafeFunction< + FnArgs<(u32, u32, PasskeyAssertionWithoutUserInterfaceRequest)>, + >, + #[napi( + ts_arg_type = "(error: null | Error, clientId: number, sequenceNumber: number, message: NativeStatus) => void" + )] + native_status_callback: ThreadsafeFunction<(u32, u32, NativeStatus)>, + ) -> napi::Result { + let (send, mut recv) = tokio::sync::mpsc::channel::(32); + tokio::spawn(async move { + while let Some(Message { + client_id, + kind, + message, + }) = recv.recv().await + { + match kind { + // TODO: We're ignoring the connection and disconnection messages for now + MessageType::Connected | MessageType::Disconnected => continue, + MessageType::Message => { + let Some(message) = message else { + error!("Message is empty"); + continue; + }; + + match serde_json::from_str::>( + &message, + ) { + Ok(msg) => { + let value = msg + .value + .map(|value| (client_id, msg.sequence_number, value).into()) + .map_err(|e| napi::Error::from_reason(format!("{e:?}"))); + + assertion_callback + .call(value, ThreadsafeFunctionCallMode::NonBlocking); + continue; + } + Err(e) => { + error!(error = %e, "Error deserializing message1"); + } + } + + match serde_json::from_str::< + PasskeyMessage, + >(&message) + { + Ok(msg) => { + let value = msg + .value + .map(|value| (client_id, msg.sequence_number, value).into()) + .map_err(|e| napi::Error::from_reason(format!("{e:?}"))); + + assertion_without_user_interface_callback + .call(value, ThreadsafeFunctionCallMode::NonBlocking); + continue; + } + Err(e) => { + error!(error = %e, "Error deserializing message1"); + } + } + + match serde_json::from_str::>( + &message, + ) { + Ok(msg) => { + let value = msg + .value + .map(|value| (client_id, msg.sequence_number, value).into()) + .map_err(|e| napi::Error::from_reason(format!("{e:?}"))); + registration_callback + .call(value, ThreadsafeFunctionCallMode::NonBlocking); + continue; + } + Err(e) => { + error!(error = %e, "Error deserializing message2"); + } + } + + match serde_json::from_str::>(&message) { + Ok(msg) => { + let value = msg + .value + .map(|value| (client_id, msg.sequence_number, value)) + .map_err(|e| napi::Error::from_reason(format!("{e:?}"))); + native_status_callback + .call(value, ThreadsafeFunctionCallMode::NonBlocking); + continue; + } + Err(error) => { + error!(%error, "Unable to deserialze native status."); + } + } + + error!(message, "Received an unknown message2"); + } + } + } + }); + + let path = desktop_core::ipc::path(&name); + + let server = desktop_core::ipc::server::Server::start(&path, send).map_err(|e| { + napi::Error::from_reason(format!( + "Error listening to server - Path: {path:?} - Error: {e} - {e:?}" + )) + })?; + + Ok(AutofillIpcServer { server }) + } + + /// Return the path to the IPC server. + #[napi] + pub fn get_path(&self) -> String { + self.server.path.to_string_lossy().to_string() + } + + /// Stop the IPC server. + #[napi] + pub fn stop(&self) -> napi::Result<()> { + self.server.stop(); + Ok(()) + } + + #[napi] + pub fn complete_registration( + &self, + client_id: u32, + sequence_number: u32, + response: PasskeyRegistrationResponse, + ) -> napi::Result { + let message = PasskeyMessage { + sequence_number, + value: Ok(response), + }; + self.send(client_id, serde_json::to_string(&message).unwrap()) + } + + #[napi] + pub fn complete_assertion( + &self, + client_id: u32, + sequence_number: u32, + response: PasskeyAssertionResponse, + ) -> napi::Result { + let message = PasskeyMessage { + sequence_number, + value: Ok(response), + }; + self.send(client_id, serde_json::to_string(&message).unwrap()) + } + + #[napi] + pub fn complete_error( + &self, + client_id: u32, + sequence_number: u32, + error: String, + ) -> napi::Result { + let message: PasskeyMessage<()> = PasskeyMessage { + sequence_number, + value: Err(BitwardenError::Internal(error)), + }; + self.send(client_id, serde_json::to_string(&message).unwrap()) + } + + // TODO: Add a way to send a message to a specific client? + fn send(&self, _client_id: u32, message: String) -> napi::Result { + self.server + .send(message) + .map_err(|e| { + napi::Error::from_reason(format!("Error sending message - Error: {e} - {e:?}")) + }) + // NAPI doesn't support u64 or usize, so we need to convert to u32 + .map(|u| u32::try_from(u).unwrap_or_default()) + } + } +} + +#[napi] +pub mod passkey_authenticator { + #[napi] + pub fn register() -> napi::Result<()> { + crate::passkey_authenticator_internal::register().map_err(|e| { + napi::Error::from_reason(format!("Passkey registration failed - Error: {e} - {e:?}")) + }) + } +} + +#[napi] +pub mod logging { + //! `logging` is the interface between the native desktop's usage of the `tracing` crate + //! for logging, to intercept events and write to the JS space. + //! + //! # Example + //! + //! [Elec] 14:34:03.517 › [NAPI] [INFO] desktop_core::ssh_agent::platform_ssh_agent: Starting + //! SSH Agent server {socket=/Users/foo/.bitwarden-ssh-agent.sock} + + use std::{fmt::Write, sync::OnceLock}; + + use napi::{ + bindgen_prelude::FnArgs, + threadsafe_function::{ThreadsafeFunction, ThreadsafeFunctionCallMode}, + }; + use tracing::Level; + use tracing_subscriber::{ + filter::EnvFilter, + fmt::format::{DefaultVisitor, Writer}, + layer::SubscriberExt, + util::SubscriberInitExt, + Layer, + }; + + struct JsLogger(OnceLock>>); + static JS_LOGGER: JsLogger = JsLogger(OnceLock::new()); + + #[napi] + pub enum LogLevel { + Trace, + Debug, + Info, + Warn, + Error, + } + + impl From<&Level> for LogLevel { + fn from(level: &Level) -> Self { + match *level { + Level::TRACE => LogLevel::Trace, + Level::DEBUG => LogLevel::Debug, + Level::INFO => LogLevel::Info, + Level::WARN => LogLevel::Warn, + Level::ERROR => LogLevel::Error, + } + } + } + + // JsLayer lets us intercept events and write them to the JS Logger. + struct JsLayer; + + impl Layer for JsLayer + where + S: tracing::Subscriber, + { + // This function builds a log message buffer from the event data and + // calls the JS logger with it. + // + // For example, this log call: + // + // ``` + // mod supreme { + // mod module { + // let foo = "bar"; + // info!(best_variable_name = %foo, "Foo done it again."); + // } + // } + // ``` + // + // , results in the following string: + // + // [INFO] supreme::module: Foo done it again. {best_variable_name=bar} + fn on_event( + &self, + event: &tracing::Event<'_>, + _ctx: tracing_subscriber::layer::Context<'_, S>, + ) { + let mut buffer = String::new(); + + // create the preamble text that precedes the message and vars. e.g.: + // [INFO] desktop_core::ssh_agent::platform_ssh_agent: + let level = event.metadata().level().as_str(); + let module_path = event.metadata().module_path().unwrap_or_default(); + + write!(&mut buffer, "[{level}] {module_path}:") + .expect("Failed to write tracing event to buffer"); + + let writer = Writer::new(&mut buffer); + + // DefaultVisitor adds the message and variables to the buffer + let mut visitor = DefaultVisitor::new(writer, false); + event.record(&mut visitor); + + let msg = (event.metadata().level().into(), buffer); + + if let Some(logger) = JS_LOGGER.0.get() { + let _ = logger.call(Ok(msg.into()), ThreadsafeFunctionCallMode::NonBlocking); + }; + } + } + + #[napi] + pub fn init_napi_log(js_log_fn: ThreadsafeFunction>) { + let _ = JS_LOGGER.0.set(js_log_fn); + + // the log level hierarchy is determined by: + // - if RUST_LOG is detected at runtime + // - if RUST_LOG is provided at compile time + // - default to INFO + let filter = EnvFilter::builder() + .with_default_directive( + option_env!("RUST_LOG") + .unwrap_or("info") + .parse() + .expect("should provide valid log level at compile time."), + ) + // parse directives from the RUST_LOG environment variable, + // overriding the default directive for matching targets. + .from_env_lossy(); + + // With the `tracing-log` feature enabled for the `tracing_subscriber`, + // the registry below will initialize a log compatibility layer, which allows + // the subscriber to consume log::Records as though they were tracing Events. + // https://docs.rs/tracing-subscriber/latest/tracing_subscriber/util/trait.SubscriberInitExt.html#method.init + tracing_subscriber::registry() + .with(filter) + .with(JsLayer) + .init(); + } +} + +#[napi] +pub mod chromium_importer { + use std::collections::HashMap; + + use chromium_importer::{ + chromium::{ + DefaultInstalledBrowserRetriever, LoginImportResult as _LoginImportResult, + ProfileInfo as _ProfileInfo, + }, + metadata::NativeImporterMetadata as _NativeImporterMetadata, + }; + + #[napi(object)] + pub struct ProfileInfo { + pub id: String, + pub name: String, + } + + #[napi(object)] + pub struct Login { + pub url: String, + pub username: String, + pub password: String, + pub note: String, + } + + #[napi(object)] + pub struct LoginImportFailure { + pub url: String, + pub username: String, + pub error: String, + } + + #[napi(object)] + pub struct LoginImportResult { + pub login: Option, + pub failure: Option, + } + + #[napi(object)] + pub struct NativeImporterMetadata { + pub id: String, + pub loaders: Vec, + pub instructions: String, + } + + impl From<_LoginImportResult> for LoginImportResult { + fn from(l: _LoginImportResult) -> Self { + match l { + _LoginImportResult::Success(l) => LoginImportResult { + login: Some(Login { + url: l.url, + username: l.username, + password: l.password, + note: l.note, + }), + failure: None, + }, + _LoginImportResult::Failure(l) => LoginImportResult { + login: None, + failure: Some(LoginImportFailure { + url: l.url, + username: l.username, + error: l.error, + }), + }, + } + } + } + + impl From<_ProfileInfo> for ProfileInfo { + fn from(p: _ProfileInfo) -> Self { + ProfileInfo { + id: p.folder, + name: p.name, + } + } + } + + impl From<_NativeImporterMetadata> for NativeImporterMetadata { + fn from(m: _NativeImporterMetadata) -> Self { + NativeImporterMetadata { + id: m.id, + loaders: m.loaders, + instructions: m.instructions, + } + } + } + + #[napi] + /// Returns OS aware metadata describing supported Chromium based importers as a JSON string. + pub fn get_metadata() -> HashMap { + chromium_importer::metadata::get_supported_importers::() + .into_iter() + .map(|(browser, metadata)| (browser, NativeImporterMetadata::from(metadata))) + .collect() + } + + #[napi] + pub fn get_available_profiles(browser: String) -> napi::Result> { + chromium_importer::chromium::get_available_profiles(&browser) + .map(|profiles| profiles.into_iter().map(ProfileInfo::from).collect()) + .map_err(|e| napi::Error::from_reason(e.to_string())) + } + + #[napi] + pub async fn import_logins( + browser: String, + profile_id: String, + ) -> napi::Result> { + chromium_importer::chromium::import_logins(&browser, &profile_id) + .await + .map(|logins| logins.into_iter().map(LoginImportResult::from).collect()) + .map_err(|e| napi::Error::from_reason(e.to_string())) + } +} + +#[napi] +pub mod autotype { + #[napi] + pub fn get_foreground_window_title() -> napi::Result { + autotype::get_foreground_window_title().map_err(|_| { + napi::Error::from_reason( + "Autotype Error: failed to get foreground window title".to_string(), + ) + }) + } + + #[napi] + pub fn type_input( + input: Vec, + keyboard_shortcut: Vec, + ) -> napi::Result<(), napi::Status> { + autotype::type_input(&input, &keyboard_shortcut) + .map_err(|e| napi::Error::from_reason(format!("Autotype Error: {e}"))) + } +} diff --git a/apps/desktop/desktop_native/napi/src/logging.rs b/apps/desktop/desktop_native/napi/src/logging.rs deleted file mode 100644 index e5791065e4e..00000000000 --- a/apps/desktop/desktop_native/napi/src/logging.rs +++ /dev/null @@ -1,131 +0,0 @@ -#[napi] -pub mod logging { - //! `logging` is the interface between the native desktop's usage of the `tracing` crate - //! for logging, to intercept events and write to the JS space. - //! - //! # Example - //! - //! [Elec] 14:34:03.517 › [NAPI] [INFO] desktop_core::ssh_agent::platform_ssh_agent: Starting - //! SSH Agent server {socket=/Users/foo/.bitwarden-ssh-agent.sock} - - use std::{fmt::Write, sync::OnceLock}; - - use napi::{ - bindgen_prelude::FnArgs, - threadsafe_function::{ThreadsafeFunction, ThreadsafeFunctionCallMode}, - }; - use tracing::Level; - use tracing_subscriber::{ - filter::EnvFilter, - fmt::format::{DefaultVisitor, Writer}, - layer::SubscriberExt, - util::SubscriberInitExt, - Layer, - }; - - struct JsLogger(OnceLock>>); - static JS_LOGGER: JsLogger = JsLogger(OnceLock::new()); - - #[napi] - pub enum LogLevel { - Trace, - Debug, - Info, - Warn, - Error, - } - - impl From<&Level> for LogLevel { - fn from(level: &Level) -> Self { - match *level { - Level::TRACE => LogLevel::Trace, - Level::DEBUG => LogLevel::Debug, - Level::INFO => LogLevel::Info, - Level::WARN => LogLevel::Warn, - Level::ERROR => LogLevel::Error, - } - } - } - - // JsLayer lets us intercept events and write them to the JS Logger. - struct JsLayer; - - impl Layer for JsLayer - where - S: tracing::Subscriber, - { - // This function builds a log message buffer from the event data and - // calls the JS logger with it. - // - // For example, this log call: - // - // ``` - // mod supreme { - // mod module { - // let foo = "bar"; - // info!(best_variable_name = %foo, "Foo done it again."); - // } - // } - // ``` - // - // , results in the following string: - // - // [INFO] supreme::module: Foo done it again. {best_variable_name=bar} - fn on_event( - &self, - event: &tracing::Event<'_>, - _ctx: tracing_subscriber::layer::Context<'_, S>, - ) { - let mut buffer = String::new(); - - // create the preamble text that precedes the message and vars. e.g.: - // [INFO] desktop_core::ssh_agent::platform_ssh_agent: - let level = event.metadata().level().as_str(); - let module_path = event.metadata().module_path().unwrap_or_default(); - - write!(&mut buffer, "[{level}] {module_path}:") - .expect("Failed to write tracing event to buffer"); - - let writer = Writer::new(&mut buffer); - - // DefaultVisitor adds the message and variables to the buffer - let mut visitor = DefaultVisitor::new(writer, false); - event.record(&mut visitor); - - let msg = (event.metadata().level().into(), buffer); - - if let Some(logger) = JS_LOGGER.0.get() { - let _ = logger.call(Ok(msg.into()), ThreadsafeFunctionCallMode::NonBlocking); - }; - } - } - - #[napi] - pub fn init_napi_log(js_log_fn: ThreadsafeFunction>) { - let _ = JS_LOGGER.0.set(js_log_fn); - - // the log level hierarchy is determined by: - // - if RUST_LOG is detected at runtime - // - if RUST_LOG is provided at compile time - // - default to INFO - let filter = EnvFilter::builder() - .with_default_directive( - option_env!("RUST_LOG") - .unwrap_or("info") - .parse() - .expect("should provide valid log level at compile time."), - ) - // parse directives from the RUST_LOG environment variable, - // overriding the default directive for matching targets. - .from_env_lossy(); - - // With the `tracing-log` feature enabled for the `tracing_subscriber`, - // the registry below will initialize a log compatibility layer, which allows - // the subscriber to consume log::Records as though they were tracing Events. - // https://docs.rs/tracing-subscriber/latest/tracing_subscriber/util/trait.SubscriberInitExt.html#method.init - tracing_subscriber::registry() - .with(filter) - .with(JsLayer) - .init(); - } -} diff --git a/apps/desktop/desktop_native/napi/src/passkey_authenticator.rs b/apps/desktop/desktop_native/napi/src/passkey_authenticator.rs deleted file mode 100644 index 37796353b80..00000000000 --- a/apps/desktop/desktop_native/napi/src/passkey_authenticator.rs +++ /dev/null @@ -1,9 +0,0 @@ -#[napi] -pub mod passkey_authenticator { - #[napi] - pub fn register() -> napi::Result<()> { - crate::passkey_authenticator_internal::register().map_err(|e| { - napi::Error::from_reason(format!("Passkey registration failed - Error: {e} - {e:?}")) - }) - } -} diff --git a/apps/desktop/desktop_native/napi/src/passwords.rs b/apps/desktop/desktop_native/napi/src/passwords.rs deleted file mode 100644 index 763f338b0cb..00000000000 --- a/apps/desktop/desktop_native/napi/src/passwords.rs +++ /dev/null @@ -1,46 +0,0 @@ -#[napi] -pub mod passwords { - - /// The error message returned when a password is not found during retrieval or deletion. - #[napi] - pub const PASSWORD_NOT_FOUND: &str = desktop_core::password::PASSWORD_NOT_FOUND; - - /// Fetch the stored password from the keychain. - /// Throws {@link Error} with message {@link PASSWORD_NOT_FOUND} if the password does not exist. - #[napi] - pub async fn get_password(service: String, account: String) -> napi::Result { - desktop_core::password::get_password(&service, &account) - .await - .map_err(|e| napi::Error::from_reason(e.to_string())) - } - - /// Save the password to the keychain. Adds an entry if none exists otherwise updates the - /// existing entry. - #[napi] - pub async fn set_password( - service: String, - account: String, - password: String, - ) -> napi::Result<()> { - desktop_core::password::set_password(&service, &account, &password) - .await - .map_err(|e| napi::Error::from_reason(e.to_string())) - } - - /// Delete the stored password from the keychain. - /// Throws {@link Error} with message {@link PASSWORD_NOT_FOUND} if the password does not exist. - #[napi] - pub async fn delete_password(service: String, account: String) -> napi::Result<()> { - desktop_core::password::delete_password(&service, &account) - .await - .map_err(|e| napi::Error::from_reason(e.to_string())) - } - - /// Checks if the os secure storage is available - #[napi] - pub async fn is_available() -> napi::Result { - desktop_core::password::is_available() - .await - .map_err(|e| napi::Error::from_reason(e.to_string())) - } -} diff --git a/apps/desktop/desktop_native/napi/src/powermonitors.rs b/apps/desktop/desktop_native/napi/src/powermonitors.rs deleted file mode 100644 index eb673bdbe68..00000000000 --- a/apps/desktop/desktop_native/napi/src/powermonitors.rs +++ /dev/null @@ -1,26 +0,0 @@ -#[napi] -pub mod powermonitors { - use napi::{ - threadsafe_function::{ThreadsafeFunction, ThreadsafeFunctionCallMode}, - tokio, - }; - - #[napi] - pub async fn on_lock(callback: ThreadsafeFunction<()>) -> napi::Result<()> { - let (tx, mut rx) = tokio::sync::mpsc::channel::<()>(32); - desktop_core::powermonitor::on_lock(tx) - .await - .map_err(|e| napi::Error::from_reason(e.to_string()))?; - tokio::spawn(async move { - while let Some(()) = rx.recv().await { - callback.call(Ok(()), ThreadsafeFunctionCallMode::NonBlocking); - } - }); - Ok(()) - } - - #[napi] - pub async fn is_lock_monitor_available() -> napi::Result { - Ok(desktop_core::powermonitor::is_lock_monitor_available().await) - } -} diff --git a/apps/desktop/desktop_native/napi/src/processisolations.rs b/apps/desktop/desktop_native/napi/src/processisolations.rs deleted file mode 100644 index 6ab4a2a645d..00000000000 --- a/apps/desktop/desktop_native/napi/src/processisolations.rs +++ /dev/null @@ -1,23 +0,0 @@ -#[napi] -pub mod processisolations { - #[allow(clippy::unused_async)] // FIXME: Remove unused async! - #[napi] - pub async fn disable_coredumps() -> napi::Result<()> { - desktop_core::process_isolation::disable_coredumps() - .map_err(|e| napi::Error::from_reason(e.to_string())) - } - - #[allow(clippy::unused_async)] // FIXME: Remove unused async! - #[napi] - pub async fn is_core_dumping_disabled() -> napi::Result { - desktop_core::process_isolation::is_core_dumping_disabled() - .map_err(|e| napi::Error::from_reason(e.to_string())) - } - - #[allow(clippy::unused_async)] // FIXME: Remove unused async! - #[napi] - pub async fn isolate_process() -> napi::Result<()> { - desktop_core::process_isolation::isolate_process() - .map_err(|e| napi::Error::from_reason(e.to_string())) - } -} diff --git a/apps/desktop/desktop_native/napi/src/sshagent.rs b/apps/desktop/desktop_native/napi/src/sshagent.rs deleted file mode 100644 index 83eec090302..00000000000 --- a/apps/desktop/desktop_native/napi/src/sshagent.rs +++ /dev/null @@ -1,163 +0,0 @@ -#[napi] -pub mod sshagent { - use std::sync::Arc; - - use napi::{ - bindgen_prelude::Promise, - threadsafe_function::{ThreadsafeFunction, ThreadsafeFunctionCallMode}, - }; - use tokio::{self, sync::Mutex}; - use tracing::error; - - #[napi] - pub struct SshAgentState { - state: desktop_core::ssh_agent::BitwardenDesktopAgent, - } - - #[napi(object)] - pub struct PrivateKey { - pub private_key: String, - pub name: String, - pub cipher_id: String, - } - - #[napi(object)] - pub struct SshKey { - pub private_key: String, - pub public_key: String, - pub key_fingerprint: String, - } - - #[napi(object)] - pub struct SshUIRequest { - pub cipher_id: Option, - pub is_list: bool, - pub process_name: String, - pub is_forwarding: bool, - pub namespace: Option, - } - - #[allow(clippy::unused_async)] // FIXME: Remove unused async! - #[napi] - pub async fn serve( - callback: ThreadsafeFunction>, - ) -> napi::Result { - let (auth_request_tx, mut auth_request_rx) = - tokio::sync::mpsc::channel::(32); - let (auth_response_tx, auth_response_rx) = - tokio::sync::broadcast::channel::<(u32, bool)>(32); - let auth_response_tx_arc = Arc::new(Mutex::new(auth_response_tx)); - // Wrap callback in Arc so it can be shared across spawned tasks - let callback = Arc::new(callback); - tokio::spawn(async move { - let _ = auth_response_rx; - - while let Some(request) = auth_request_rx.recv().await { - let cloned_response_tx_arc = auth_response_tx_arc.clone(); - let cloned_callback = callback.clone(); - tokio::spawn(async move { - let auth_response_tx_arc = cloned_response_tx_arc; - let callback = cloned_callback; - // In NAPI v3, obtain the JS callback return as a Promise and await it - // in Rust - let (tx, rx) = std::sync::mpsc::channel::>(); - let status = callback.call_with_return_value( - Ok(SshUIRequest { - cipher_id: request.cipher_id, - is_list: request.is_list, - process_name: request.process_name, - is_forwarding: request.is_forwarding, - namespace: request.namespace, - }), - ThreadsafeFunctionCallMode::Blocking, - move |ret: Result, napi::Error>, _env| { - if let Ok(p) = ret { - let _ = tx.send(p); - } - Ok(()) - }, - ); - - let result = if status == napi::Status::Ok { - match rx.recv() { - Ok(promise) => match promise.await { - Ok(v) => v, - Err(e) => { - error!(error = %e, "UI callback promise rejected"); - false - } - }, - Err(e) => { - error!(error = %e, "Failed to receive UI callback promise"); - false - } - } - } else { - error!(error = ?status, "Calling UI callback failed"); - false - }; - - let _ = auth_response_tx_arc - .lock() - .await - .send((request.request_id, result)) - .expect("should be able to send auth response to agent"); - }); - } - }); - - match desktop_core::ssh_agent::BitwardenDesktopAgent::start_server( - auth_request_tx, - Arc::new(Mutex::new(auth_response_rx)), - ) { - Ok(state) => Ok(SshAgentState { state }), - Err(e) => Err(napi::Error::from_reason(e.to_string())), - } - } - - #[napi] - pub fn stop(agent_state: &mut SshAgentState) -> napi::Result<()> { - let bitwarden_agent_state = &mut agent_state.state; - bitwarden_agent_state.stop(); - Ok(()) - } - - #[napi] - pub fn is_running(agent_state: &mut SshAgentState) -> bool { - let bitwarden_agent_state = agent_state.state.clone(); - bitwarden_agent_state.is_running() - } - - #[napi] - pub fn set_keys( - agent_state: &mut SshAgentState, - new_keys: Vec, - ) -> napi::Result<()> { - let bitwarden_agent_state = &mut agent_state.state; - bitwarden_agent_state - .set_keys( - new_keys - .iter() - .map(|k| (k.private_key.clone(), k.name.clone(), k.cipher_id.clone())) - .collect(), - ) - .map_err(|e| napi::Error::from_reason(e.to_string()))?; - Ok(()) - } - - #[napi] - pub fn lock(agent_state: &mut SshAgentState) -> napi::Result<()> { - let bitwarden_agent_state = &mut agent_state.state; - bitwarden_agent_state - .lock() - .map_err(|e| napi::Error::from_reason(e.to_string())) - } - - #[napi] - pub fn clear_keys(agent_state: &mut SshAgentState) -> napi::Result<()> { - let bitwarden_agent_state = &mut agent_state.state; - bitwarden_agent_state - .clear_keys() - .map_err(|e| napi::Error::from_reason(e.to_string())) - } -} diff --git a/apps/desktop/desktop_native/napi/src/windows_registry.rs b/apps/desktop/desktop_native/napi/src/windows_registry.rs deleted file mode 100644 index e22e2ce46f5..00000000000 --- a/apps/desktop/desktop_native/napi/src/windows_registry.rs +++ /dev/null @@ -1,16 +0,0 @@ -#[napi] -pub mod windows_registry { - #[allow(clippy::unused_async)] // FIXME: Remove unused async! - #[napi] - pub async fn create_key(key: String, subkey: String, value: String) -> napi::Result<()> { - crate::registry::create_key(&key, &subkey, &value) - .map_err(|e| napi::Error::from_reason(e.to_string())) - } - - #[allow(clippy::unused_async)] // FIXME: Remove unused async! - #[napi] - pub async fn delete_key(key: String, subkey: String) -> napi::Result<()> { - crate::registry::delete_key(&key, &subkey) - .map_err(|e| napi::Error::from_reason(e.to_string())) - } -} diff --git a/apps/desktop/electron-builder.beta.json b/apps/desktop/electron-builder.beta.json index 9c66b17aa1f..f0746e6d408 100644 --- a/apps/desktop/electron-builder.beta.json +++ b/apps/desktop/electron-builder.beta.json @@ -61,7 +61,6 @@ "appx": { "artifactName": "Bitwarden-Beta-${version}-${arch}.${ext}", "backgroundColor": "#175DDC", - "customManifestPath": "./custom-appx-manifest.xml", "applicationId": "BitwardenBeta", "identityName": "8bitSolutionsLLC.BitwardenBeta", "publisher": "CN=14D52771-DE3C-4886-B8BF-825BA7690418", diff --git a/apps/desktop/electron-builder.json b/apps/desktop/electron-builder.json index 151ce72182d..f876b7ff680 100644 --- a/apps/desktop/electron-builder.json +++ b/apps/desktop/electron-builder.json @@ -176,7 +176,6 @@ "appx": { "artifactName": "${productName}-${version}-${arch}.${ext}", "backgroundColor": "#175DDC", - "customManifestPath": "./custom-appx-manifest.xml", "applicationId": "bitwardendesktop", "identityName": "8bitSolutionsLLC.bitwardendesktop", "publisher": "CN=14D52771-DE3C-4886-B8BF-825BA7690418", diff --git a/apps/desktop/package.json b/apps/desktop/package.json index 493458cc84b..03b86830faf 100644 --- a/apps/desktop/package.json +++ b/apps/desktop/package.json @@ -1,7 +1,7 @@ { "name": "@bitwarden/desktop", "description": "A secure and free password manager for all of your devices.", - "version": "2026.2.0", + "version": "2026.2.1", "keywords": [ "bitwarden", "password", diff --git a/apps/desktop/scripts/appx-cross-build.ps1 b/apps/desktop/scripts/appx-cross-build.ps1 index ef2ab09104c..c47567695ed 100755 --- a/apps/desktop/scripts/appx-cross-build.ps1 +++ b/apps/desktop/scripts/appx-cross-build.ps1 @@ -72,6 +72,7 @@ param( # Whether to build in release mode. $Release=$false ) + $ErrorActionPreference = "Stop" $PSNativeCommandUseErrorActionPreference = $true $startTime = Get-Date @@ -113,7 +114,7 @@ else { $builderConfig = Get-Content $electronConfigFile | ConvertFrom-Json $packageConfig = Get-Content package.json | ConvertFrom-Json -$manifestTemplate = Get-Content $builderConfig.appx.customManifestPath +$manifestTemplate = Get-Content ($builderConfig.appx.customManifestPath ?? "custom-appx-manifest.xml") $srcDir = Get-Location $assetsDir = Get-Item $builderConfig.directories.buildResources diff --git a/apps/desktop/src/app/tools/generator/credential-generator.component.html b/apps/desktop/src/app/tools/generator/credential-generator.component.html index 12088a147c5..241d21b1bb7 100644 --- a/apps/desktop/src/app/tools/generator/credential-generator.component.html +++ b/apps/desktop/src/app/tools/generator/credential-generator.component.html @@ -5,14 +5,17 @@ diff --git a/apps/desktop/src/app/tools/generator/credential-generator.component.ts b/apps/desktop/src/app/tools/generator/credential-generator.component.ts index 42313c48f7f..036a5e104aa 100644 --- a/apps/desktop/src/app/tools/generator/credential-generator.component.ts +++ b/apps/desktop/src/app/tools/generator/credential-generator.component.ts @@ -1,13 +1,7 @@ import { Component } from "@angular/core"; import { JslibModule } from "@bitwarden/angular/jslib.module"; -import { - ButtonModule, - DialogModule, - DialogService, - ItemModule, - LinkModule, -} from "@bitwarden/components"; +import { ButtonModule, DialogModule, DialogService, ItemModule } from "@bitwarden/components"; import { CredentialGeneratorHistoryDialogComponent, GeneratorModule, @@ -18,7 +12,7 @@ import { @Component({ selector: "credential-generator", templateUrl: "credential-generator.component.html", - imports: [DialogModule, ButtonModule, JslibModule, GeneratorModule, ItemModule, LinkModule], + imports: [DialogModule, ButtonModule, JslibModule, GeneratorModule, ItemModule], }) export class CredentialGeneratorComponent { constructor(private dialogService: DialogService) {} diff --git a/apps/desktop/src/app/tools/send-v2/send-v2.component.ts b/apps/desktop/src/app/tools/send-v2/send-v2.component.ts index 271418ae5b2..fc058c1a17f 100644 --- a/apps/desktop/src/app/tools/send-v2/send-v2.component.ts +++ b/apps/desktop/src/app/tools/send-v2/send-v2.component.ts @@ -1,6 +1,6 @@ // FIXME: Update this file to be type safe and remove this and next line // @ts-strict-ignore -import { Component, computed, inject, signal, viewChild } from "@angular/core"; +import { Component, computed, DestroyRef, inject, signal, viewChild } from "@angular/core"; import { toSignal } from "@angular/core/rxjs-interop"; import { combineLatest, map, switchMap, lastValueFrom } from "rxjs"; @@ -20,7 +20,7 @@ import { SendApiService } from "@bitwarden/common/tools/send/services/send-api.s import { SendType } from "@bitwarden/common/tools/send/types/send-type"; import { SendId } from "@bitwarden/common/types/guid"; import { PremiumUpgradePromptService } from "@bitwarden/common/vault/abstractions/premium-upgrade-prompt.service"; -import { ButtonModule, DialogService, ToastService } from "@bitwarden/components"; +import { ButtonModule, DialogRef, DialogService, ToastService } from "@bitwarden/components"; import { NewSendDropdownV2Component, SendItemsService, @@ -28,6 +28,7 @@ import { SendListState, SendAddEditDialogComponent, DefaultSendFormConfigService, + SendItemDialogResult, } from "@bitwarden/send-ui"; import { DesktopPremiumUpgradePromptService } from "../../../services/desktop-premium-upgrade-prompt.service"; @@ -84,6 +85,9 @@ export class SendV2Component { private dialogService = inject(DialogService); private toastService = inject(ToastService); private logService = inject(LogService); + private destroyRef = inject(DestroyRef); + + private activeDrawerRef?: DialogRef; protected readonly useDrawerEditMode = toSignal( this.configService.getFeatureFlag$(FeatureFlag.DesktopUiMigrationMilestone2), @@ -128,6 +132,12 @@ export class SendV2Component { { initialValue: null }, ); + constructor() { + this.destroyRef.onDestroy(() => { + this.activeDrawerRef?.close(); + }); + } + protected readonly selectedSendType = computed(() => { const action = this.action(); @@ -143,11 +153,12 @@ export class SendV2Component { if (this.useDrawerEditMode()) { const formConfig = await this.sendFormConfigService.buildConfig("add", undefined, type); - const dialogRef = SendAddEditDialogComponent.openDrawer(this.dialogService, { + this.activeDrawerRef = SendAddEditDialogComponent.openDrawer(this.dialogService, { formConfig, }); - await lastValueFrom(dialogRef.closed); + await lastValueFrom(this.activeDrawerRef.closed); + this.activeDrawerRef = null; } else { this.action.set(Action.Add); this.sendId.set(null); @@ -173,11 +184,12 @@ export class SendV2Component { if (this.useDrawerEditMode()) { const formConfig = await this.sendFormConfigService.buildConfig("edit", sendId as SendId); - const dialogRef = SendAddEditDialogComponent.openDrawer(this.dialogService, { + this.activeDrawerRef = SendAddEditDialogComponent.openDrawer(this.dialogService, { formConfig, }); - await lastValueFrom(dialogRef.closed); + await lastValueFrom(this.activeDrawerRef.closed); + this.activeDrawerRef = null; } else { if (sendId === this.sendId() && this.action() === Action.Edit) { return; diff --git a/apps/desktop/src/autofill/modal/credentials/fido2-create.component.ts b/apps/desktop/src/autofill/modal/credentials/fido2-create.component.ts index d18fb6752e3..d5b4984fae5 100644 --- a/apps/desktop/src/autofill/modal/credentials/fido2-create.component.ts +++ b/apps/desktop/src/autofill/modal/credentials/fido2-create.component.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { CommonModule } from "@angular/common"; import { ChangeDetectionStrategy, Component, OnInit, OnDestroy } from "@angular/core"; import { RouterModule, Router } from "@angular/router"; diff --git a/apps/desktop/src/autofill/modal/credentials/fido2-excluded-ciphers.component.ts b/apps/desktop/src/autofill/modal/credentials/fido2-excluded-ciphers.component.ts index 274956be0eb..b8aecafa92b 100644 --- a/apps/desktop/src/autofill/modal/credentials/fido2-excluded-ciphers.component.ts +++ b/apps/desktop/src/autofill/modal/credentials/fido2-excluded-ciphers.component.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { CommonModule } from "@angular/common"; import { ChangeDetectionStrategy, Component, OnInit, OnDestroy } from "@angular/core"; import { RouterModule, Router } from "@angular/router"; diff --git a/apps/desktop/src/autofill/modal/credentials/fido2-vault.component.ts b/apps/desktop/src/autofill/modal/credentials/fido2-vault.component.ts index 635ba3972cb..588f52ef6d3 100644 --- a/apps/desktop/src/autofill/modal/credentials/fido2-vault.component.ts +++ b/apps/desktop/src/autofill/modal/credentials/fido2-vault.component.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { CommonModule } from "@angular/common"; import { ChangeDetectionStrategy, Component, OnInit, OnDestroy } from "@angular/core"; import { RouterModule, Router } from "@angular/router"; diff --git a/apps/desktop/src/autofill/services/desktop-autofill.service.ts b/apps/desktop/src/autofill/services/desktop-autofill.service.ts index cca0097d65e..24052a20b3c 100644 --- a/apps/desktop/src/autofill/services/desktop-autofill.service.ts +++ b/apps/desktop/src/autofill/services/desktop-autofill.service.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { Injectable, OnDestroy } from "@angular/core"; import { Subject, @@ -51,7 +53,7 @@ import type { NativeWindowObject } from "./desktop-fido2-user-interface.service" export class DesktopAutofillService implements OnDestroy { private destroy$ = new Subject(); private registrationRequest: autofill.PasskeyRegistrationRequest; - private featureFlag?: FeatureFlag; + private featureFlag?: typeof FeatureFlag.MacOsNativeCredentialSync; private isEnabled: boolean = false; constructor( diff --git a/apps/desktop/src/autofill/services/desktop-fido2-user-interface.service.ts b/apps/desktop/src/autofill/services/desktop-fido2-user-interface.service.ts index 432448faba3..050332349a1 100644 --- a/apps/desktop/src/autofill/services/desktop-fido2-user-interface.service.ts +++ b/apps/desktop/src/autofill/services/desktop-fido2-user-interface.service.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { Router } from "@angular/router"; import { lastValueFrom, diff --git a/apps/desktop/src/entry.ts b/apps/desktop/src/entry.ts index 9f03a84e627..65b655c7478 100644 --- a/apps/desktop/src/entry.ts +++ b/apps/desktop/src/entry.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { spawn } from "child_process"; import * as path from "path"; diff --git a/apps/desktop/src/locales/af/messages.json b/apps/desktop/src/locales/af/messages.json index c0824c61d03..dfcdd36da20 100644 --- a/apps/desktop/src/locales/af/messages.json +++ b/apps/desktop/src/locales/af/messages.json @@ -1023,6 +1023,9 @@ "invalidVerificationCode": { "message": "Ongeldige bevestigingskode" }, + "invalidEmailOrVerificationCode": { + "message": "Invalid email or verification code" + }, "continue": { "message": "Gaan Voort" }, @@ -4615,7 +4618,13 @@ "enterMultipleEmailsSeparatedByComma": { "message": "Enter multiple emails by separating with a comma." }, + "emailsRequiredChangeAccessType": { + "message": "Email verification requires at least one email address. To remove all emails, change the access type above." + }, "emailPlaceholder": { "message": "user@bitwarden.com , user@acme.com" + }, + "userVerificationFailed": { + "message": "User verification failed." } } diff --git a/apps/desktop/src/locales/ar/messages.json b/apps/desktop/src/locales/ar/messages.json index 3e668c327b0..1273058bfc9 100644 --- a/apps/desktop/src/locales/ar/messages.json +++ b/apps/desktop/src/locales/ar/messages.json @@ -1023,6 +1023,9 @@ "invalidVerificationCode": { "message": "رمز التحقق غير صالح" }, + "invalidEmailOrVerificationCode": { + "message": "Invalid email or verification code" + }, "continue": { "message": "متابعة" }, @@ -4615,7 +4618,13 @@ "enterMultipleEmailsSeparatedByComma": { "message": "Enter multiple emails by separating with a comma." }, + "emailsRequiredChangeAccessType": { + "message": "Email verification requires at least one email address. To remove all emails, change the access type above." + }, "emailPlaceholder": { "message": "user@bitwarden.com , user@acme.com" + }, + "userVerificationFailed": { + "message": "User verification failed." } } diff --git a/apps/desktop/src/locales/az/messages.json b/apps/desktop/src/locales/az/messages.json index 4e5d414eb1c..ba43fecfc60 100644 --- a/apps/desktop/src/locales/az/messages.json +++ b/apps/desktop/src/locales/az/messages.json @@ -1023,6 +1023,9 @@ "invalidVerificationCode": { "message": "Yararsız doğrulama kodu" }, + "invalidEmailOrVerificationCode": { + "message": "Invalid email or verification code" + }, "continue": { "message": "Davam" }, @@ -4615,7 +4618,13 @@ "enterMultipleEmailsSeparatedByComma": { "message": "Birdən çox e-poçtu daxil edərkən vergül istifadə edin." }, + "emailsRequiredChangeAccessType": { + "message": "Email verification requires at least one email address. To remove all emails, change the access type above." + }, "emailPlaceholder": { "message": "user@bitwarden.com , user@acme.com" + }, + "userVerificationFailed": { + "message": "User verification failed." } } diff --git a/apps/desktop/src/locales/be/messages.json b/apps/desktop/src/locales/be/messages.json index f2f9d0a736d..c554352c438 100644 --- a/apps/desktop/src/locales/be/messages.json +++ b/apps/desktop/src/locales/be/messages.json @@ -1023,6 +1023,9 @@ "invalidVerificationCode": { "message": "Памылковы праверачны код" }, + "invalidEmailOrVerificationCode": { + "message": "Invalid email or verification code" + }, "continue": { "message": "Працягнуць" }, @@ -4615,7 +4618,13 @@ "enterMultipleEmailsSeparatedByComma": { "message": "Enter multiple emails by separating with a comma." }, + "emailsRequiredChangeAccessType": { + "message": "Email verification requires at least one email address. To remove all emails, change the access type above." + }, "emailPlaceholder": { "message": "user@bitwarden.com , user@acme.com" + }, + "userVerificationFailed": { + "message": "User verification failed." } } diff --git a/apps/desktop/src/locales/bg/messages.json b/apps/desktop/src/locales/bg/messages.json index ea0355ad7f6..6913b3b563e 100644 --- a/apps/desktop/src/locales/bg/messages.json +++ b/apps/desktop/src/locales/bg/messages.json @@ -1023,6 +1023,9 @@ "invalidVerificationCode": { "message": "Грешен код за потвърждаване" }, + "invalidEmailOrVerificationCode": { + "message": "Грешна е-поща или код за потвърждаване" + }, "continue": { "message": "Продължаване" }, @@ -4615,7 +4618,13 @@ "enterMultipleEmailsSeparatedByComma": { "message": "Можете да въведете повече е-пощи, като ги разделите със запетая." }, + "emailsRequiredChangeAccessType": { + "message": "Потвърждаването на е-пощата изисква да е наличен поне един адрес на е-поща. Ако искате да премахнете всички е-пощи, променете начина за достъп по-горе." + }, "emailPlaceholder": { "message": "потребител@bitwarden.com , потребител@acme.com" + }, + "userVerificationFailed": { + "message": "Проверката на потребителя беше неуспешна." } } diff --git a/apps/desktop/src/locales/bn/messages.json b/apps/desktop/src/locales/bn/messages.json index 6a211c93052..2919e52b0fb 100644 --- a/apps/desktop/src/locales/bn/messages.json +++ b/apps/desktop/src/locales/bn/messages.json @@ -1023,6 +1023,9 @@ "invalidVerificationCode": { "message": "Invalid verification code" }, + "invalidEmailOrVerificationCode": { + "message": "Invalid email or verification code" + }, "continue": { "message": "অবিরত" }, @@ -4615,7 +4618,13 @@ "enterMultipleEmailsSeparatedByComma": { "message": "Enter multiple emails by separating with a comma." }, + "emailsRequiredChangeAccessType": { + "message": "Email verification requires at least one email address. To remove all emails, change the access type above." + }, "emailPlaceholder": { "message": "user@bitwarden.com , user@acme.com" + }, + "userVerificationFailed": { + "message": "User verification failed." } } diff --git a/apps/desktop/src/locales/bs/messages.json b/apps/desktop/src/locales/bs/messages.json index 4ca3aa8ffc2..61bb17d5171 100644 --- a/apps/desktop/src/locales/bs/messages.json +++ b/apps/desktop/src/locales/bs/messages.json @@ -1023,6 +1023,9 @@ "invalidVerificationCode": { "message": "Neispravan verifikacijski kod" }, + "invalidEmailOrVerificationCode": { + "message": "Invalid email or verification code" + }, "continue": { "message": "Nastavi" }, @@ -4615,7 +4618,13 @@ "enterMultipleEmailsSeparatedByComma": { "message": "Enter multiple emails by separating with a comma." }, + "emailsRequiredChangeAccessType": { + "message": "Email verification requires at least one email address. To remove all emails, change the access type above." + }, "emailPlaceholder": { "message": "user@bitwarden.com , user@acme.com" + }, + "userVerificationFailed": { + "message": "User verification failed." } } diff --git a/apps/desktop/src/locales/ca/messages.json b/apps/desktop/src/locales/ca/messages.json index 3b8562814fd..ac59b1bd040 100644 --- a/apps/desktop/src/locales/ca/messages.json +++ b/apps/desktop/src/locales/ca/messages.json @@ -1023,6 +1023,9 @@ "invalidVerificationCode": { "message": "Codi de verificació no vàlid" }, + "invalidEmailOrVerificationCode": { + "message": "Invalid email or verification code" + }, "continue": { "message": "Continua" }, @@ -4615,7 +4618,13 @@ "enterMultipleEmailsSeparatedByComma": { "message": "Enter multiple emails by separating with a comma." }, + "emailsRequiredChangeAccessType": { + "message": "Email verification requires at least one email address. To remove all emails, change the access type above." + }, "emailPlaceholder": { "message": "user@bitwarden.com , user@acme.com" + }, + "userVerificationFailed": { + "message": "User verification failed." } } diff --git a/apps/desktop/src/locales/cs/messages.json b/apps/desktop/src/locales/cs/messages.json index 75136c41831..e5d009cbe2c 100644 --- a/apps/desktop/src/locales/cs/messages.json +++ b/apps/desktop/src/locales/cs/messages.json @@ -1023,6 +1023,9 @@ "invalidVerificationCode": { "message": "Neplatný ověřovací kód" }, + "invalidEmailOrVerificationCode": { + "message": "Neplatný e-mail nebo ověřovací kód" + }, "continue": { "message": "Pokračovat" }, @@ -1055,7 +1058,7 @@ "message": "Ověřovací aplikace" }, "authenticatorAppDescV2": { - "message": "Zadejte kód vygenerovaný ověřovací aplikací, jako je Autentikátor Bitwarden.", + "message": "Zadejte kód vygenerovaný ověřovací aplikací, jako je Bitwarden Authenticator.", "description": "'Bitwarden Authenticator' is a product name and should not be translated." }, "yubiKeyTitleV2": { @@ -4615,7 +4618,13 @@ "enterMultipleEmailsSeparatedByComma": { "message": "Zadejte více e-mailů oddělených čárkou." }, + "emailsRequiredChangeAccessType": { + "message": "Ověření e-mailu vyžaduje alespoň jednu e-mailovou adresu. Chcete-li odebrat všechny emaily, změňte výše uvedený typ přístupu." + }, "emailPlaceholder": { "message": "uživatel@bitwarden.com, uživatel@společnost.cz" + }, + "userVerificationFailed": { + "message": "Ověření uživatele se nezdařilo." } } diff --git a/apps/desktop/src/locales/cy/messages.json b/apps/desktop/src/locales/cy/messages.json index 46df0aca8c5..6f39024dd17 100644 --- a/apps/desktop/src/locales/cy/messages.json +++ b/apps/desktop/src/locales/cy/messages.json @@ -1023,6 +1023,9 @@ "invalidVerificationCode": { "message": "Invalid verification code" }, + "invalidEmailOrVerificationCode": { + "message": "Invalid email or verification code" + }, "continue": { "message": "Continue" }, @@ -4615,7 +4618,13 @@ "enterMultipleEmailsSeparatedByComma": { "message": "Enter multiple emails by separating with a comma." }, + "emailsRequiredChangeAccessType": { + "message": "Email verification requires at least one email address. To remove all emails, change the access type above." + }, "emailPlaceholder": { "message": "user@bitwarden.com , user@acme.com" + }, + "userVerificationFailed": { + "message": "User verification failed." } } diff --git a/apps/desktop/src/locales/da/messages.json b/apps/desktop/src/locales/da/messages.json index f6abcd51740..f7f6dd31da5 100644 --- a/apps/desktop/src/locales/da/messages.json +++ b/apps/desktop/src/locales/da/messages.json @@ -1023,6 +1023,9 @@ "invalidVerificationCode": { "message": "Ugyldig bekræftelseskode" }, + "invalidEmailOrVerificationCode": { + "message": "Invalid email or verification code" + }, "continue": { "message": "Fortsæt" }, @@ -4615,7 +4618,13 @@ "enterMultipleEmailsSeparatedByComma": { "message": "Enter multiple emails by separating with a comma." }, + "emailsRequiredChangeAccessType": { + "message": "Email verification requires at least one email address. To remove all emails, change the access type above." + }, "emailPlaceholder": { "message": "user@bitwarden.com , user@acme.com" + }, + "userVerificationFailed": { + "message": "User verification failed." } } diff --git a/apps/desktop/src/locales/de/messages.json b/apps/desktop/src/locales/de/messages.json index a2c346896ac..6d7f8843a32 100644 --- a/apps/desktop/src/locales/de/messages.json +++ b/apps/desktop/src/locales/de/messages.json @@ -1023,6 +1023,9 @@ "invalidVerificationCode": { "message": "Ungültiger Verifizierungscode" }, + "invalidEmailOrVerificationCode": { + "message": "E-Mail oder Verifizierungscode ungültig" + }, "continue": { "message": "Weiter" }, @@ -4615,7 +4618,13 @@ "enterMultipleEmailsSeparatedByComma": { "message": "Gib mehrere E-Mail-Adressen ein, indem du sie mit einem Komma trennst." }, + "emailsRequiredChangeAccessType": { + "message": "E-Mail-Verifizierung erfordert mindestens eine E-Mail-Adresse. Ändere den Zugriffstyp oben, um alle E-Mails zu entfernen." + }, "emailPlaceholder": { "message": "benutzer@bitwarden.com, benutzer@acme.com" + }, + "userVerificationFailed": { + "message": "Benutzerverifizierung fehlgeschlagen." } } diff --git a/apps/desktop/src/locales/el/messages.json b/apps/desktop/src/locales/el/messages.json index 624560f5888..fe21423ceba 100644 --- a/apps/desktop/src/locales/el/messages.json +++ b/apps/desktop/src/locales/el/messages.json @@ -1023,6 +1023,9 @@ "invalidVerificationCode": { "message": "Μη έγκυρος κωδικός επαλήθευσης" }, + "invalidEmailOrVerificationCode": { + "message": "Invalid email or verification code" + }, "continue": { "message": "Συνέχεια" }, @@ -4615,7 +4618,13 @@ "enterMultipleEmailsSeparatedByComma": { "message": "Enter multiple emails by separating with a comma." }, + "emailsRequiredChangeAccessType": { + "message": "Email verification requires at least one email address. To remove all emails, change the access type above." + }, "emailPlaceholder": { "message": "user@bitwarden.com , user@acme.com" + }, + "userVerificationFailed": { + "message": "User verification failed." } } diff --git a/apps/desktop/src/locales/en/messages.json b/apps/desktop/src/locales/en/messages.json index f444265877d..97a38235fd7 100644 --- a/apps/desktop/src/locales/en/messages.json +++ b/apps/desktop/src/locales/en/messages.json @@ -1023,6 +1023,9 @@ "invalidVerificationCode": { "message": "Invalid verification code" }, + "invalidEmailOrVerificationCode": { + "message": "Invalid email or verification code" + }, "continue": { "message": "Continue" }, @@ -4615,7 +4618,13 @@ "enterMultipleEmailsSeparatedByComma": { "message": "Enter multiple emails by separating with a comma." }, + "emailsRequiredChangeAccessType": { + "message": "Email verification requires at least one email address. To remove all emails, change the access type above." + }, "emailPlaceholder": { "message": "user@bitwarden.com , user@acme.com" + }, + "userVerificationFailed": { + "message": "User verification failed." } } diff --git a/apps/desktop/src/locales/en_GB/messages.json b/apps/desktop/src/locales/en_GB/messages.json index aaf1e12955c..04684ffe9bd 100644 --- a/apps/desktop/src/locales/en_GB/messages.json +++ b/apps/desktop/src/locales/en_GB/messages.json @@ -1023,6 +1023,9 @@ "invalidVerificationCode": { "message": "Invalid verification code" }, + "invalidEmailOrVerificationCode": { + "message": "Invalid email or verification code" + }, "continue": { "message": "Continue" }, @@ -4615,7 +4618,13 @@ "enterMultipleEmailsSeparatedByComma": { "message": "Enter multiple emails by separating with a comma." }, + "emailsRequiredChangeAccessType": { + "message": "Email verification requires at least one email address. To remove all emails, change the access type above." + }, "emailPlaceholder": { "message": "user@bitwarden.com , user@acme.com" + }, + "userVerificationFailed": { + "message": "User verification failed." } } diff --git a/apps/desktop/src/locales/en_IN/messages.json b/apps/desktop/src/locales/en_IN/messages.json index 1dca7070bfc..bda8ffa8fd5 100644 --- a/apps/desktop/src/locales/en_IN/messages.json +++ b/apps/desktop/src/locales/en_IN/messages.json @@ -1023,6 +1023,9 @@ "invalidVerificationCode": { "message": "Invalid verification code" }, + "invalidEmailOrVerificationCode": { + "message": "Invalid email or verification code" + }, "continue": { "message": "Continue" }, @@ -4615,7 +4618,13 @@ "enterMultipleEmailsSeparatedByComma": { "message": "Enter multiple emails by separating with a comma." }, + "emailsRequiredChangeAccessType": { + "message": "Email verification requires at least one email address. To remove all emails, change the access type above." + }, "emailPlaceholder": { "message": "user@bitwarden.com , user@acme.com" + }, + "userVerificationFailed": { + "message": "User verification failed." } } diff --git a/apps/desktop/src/locales/eo/messages.json b/apps/desktop/src/locales/eo/messages.json index cefd462e99f..79e1ece499d 100644 --- a/apps/desktop/src/locales/eo/messages.json +++ b/apps/desktop/src/locales/eo/messages.json @@ -1023,6 +1023,9 @@ "invalidVerificationCode": { "message": "Nevalida kontrola kodo" }, + "invalidEmailOrVerificationCode": { + "message": "Invalid email or verification code" + }, "continue": { "message": "Daŭrigi" }, @@ -4615,7 +4618,13 @@ "enterMultipleEmailsSeparatedByComma": { "message": "Enter multiple emails by separating with a comma." }, + "emailsRequiredChangeAccessType": { + "message": "Email verification requires at least one email address. To remove all emails, change the access type above." + }, "emailPlaceholder": { "message": "user@bitwarden.com , user@acme.com" + }, + "userVerificationFailed": { + "message": "User verification failed." } } diff --git a/apps/desktop/src/locales/es/messages.json b/apps/desktop/src/locales/es/messages.json index d9fb17907aa..91ec21c717f 100644 --- a/apps/desktop/src/locales/es/messages.json +++ b/apps/desktop/src/locales/es/messages.json @@ -1023,6 +1023,9 @@ "invalidVerificationCode": { "message": "Código de verificación incorrecto" }, + "invalidEmailOrVerificationCode": { + "message": "Invalid email or verification code" + }, "continue": { "message": "Continuar" }, @@ -4615,7 +4618,13 @@ "enterMultipleEmailsSeparatedByComma": { "message": "Introduce varios correos electrónicos separándolos con una coma." }, + "emailsRequiredChangeAccessType": { + "message": "Email verification requires at least one email address. To remove all emails, change the access type above." + }, "emailPlaceholder": { "message": "user@bitwarden.com , user@acme.com" + }, + "userVerificationFailed": { + "message": "User verification failed." } } diff --git a/apps/desktop/src/locales/et/messages.json b/apps/desktop/src/locales/et/messages.json index ba930db8961..84f432ac410 100644 --- a/apps/desktop/src/locales/et/messages.json +++ b/apps/desktop/src/locales/et/messages.json @@ -1023,6 +1023,9 @@ "invalidVerificationCode": { "message": "Vale kinnituskood" }, + "invalidEmailOrVerificationCode": { + "message": "Invalid email or verification code" + }, "continue": { "message": "Jätka" }, @@ -4615,7 +4618,13 @@ "enterMultipleEmailsSeparatedByComma": { "message": "Enter multiple emails by separating with a comma." }, + "emailsRequiredChangeAccessType": { + "message": "Email verification requires at least one email address. To remove all emails, change the access type above." + }, "emailPlaceholder": { "message": "user@bitwarden.com , user@acme.com" + }, + "userVerificationFailed": { + "message": "User verification failed." } } diff --git a/apps/desktop/src/locales/eu/messages.json b/apps/desktop/src/locales/eu/messages.json index e03da9ef685..2adb34fa9f2 100644 --- a/apps/desktop/src/locales/eu/messages.json +++ b/apps/desktop/src/locales/eu/messages.json @@ -1023,6 +1023,9 @@ "invalidVerificationCode": { "message": "Egiaztatze-kodea ez da baliozkoa" }, + "invalidEmailOrVerificationCode": { + "message": "Invalid email or verification code" + }, "continue": { "message": "Jarraitu" }, @@ -4615,7 +4618,13 @@ "enterMultipleEmailsSeparatedByComma": { "message": "Enter multiple emails by separating with a comma." }, + "emailsRequiredChangeAccessType": { + "message": "Email verification requires at least one email address. To remove all emails, change the access type above." + }, "emailPlaceholder": { "message": "user@bitwarden.com , user@acme.com" + }, + "userVerificationFailed": { + "message": "User verification failed." } } diff --git a/apps/desktop/src/locales/fa/messages.json b/apps/desktop/src/locales/fa/messages.json index a443cc8c2e7..94e5a54ab4b 100644 --- a/apps/desktop/src/locales/fa/messages.json +++ b/apps/desktop/src/locales/fa/messages.json @@ -1023,6 +1023,9 @@ "invalidVerificationCode": { "message": "کد تأیید نامعتبر است" }, + "invalidEmailOrVerificationCode": { + "message": "Invalid email or verification code" + }, "continue": { "message": "ادامه" }, @@ -4615,7 +4618,13 @@ "enterMultipleEmailsSeparatedByComma": { "message": "Enter multiple emails by separating with a comma." }, + "emailsRequiredChangeAccessType": { + "message": "Email verification requires at least one email address. To remove all emails, change the access type above." + }, "emailPlaceholder": { "message": "user@bitwarden.com , user@acme.com" + }, + "userVerificationFailed": { + "message": "User verification failed." } } diff --git a/apps/desktop/src/locales/fi/messages.json b/apps/desktop/src/locales/fi/messages.json index c7b51def9b2..78eedc7e1ce 100644 --- a/apps/desktop/src/locales/fi/messages.json +++ b/apps/desktop/src/locales/fi/messages.json @@ -1023,6 +1023,9 @@ "invalidVerificationCode": { "message": "Virheellinen todennuskoodi" }, + "invalidEmailOrVerificationCode": { + "message": "Invalid email or verification code" + }, "continue": { "message": "Jatka" }, @@ -4615,7 +4618,13 @@ "enterMultipleEmailsSeparatedByComma": { "message": "Enter multiple emails by separating with a comma." }, + "emailsRequiredChangeAccessType": { + "message": "Email verification requires at least one email address. To remove all emails, change the access type above." + }, "emailPlaceholder": { "message": "user@bitwarden.com , user@acme.com" + }, + "userVerificationFailed": { + "message": "User verification failed." } } diff --git a/apps/desktop/src/locales/fil/messages.json b/apps/desktop/src/locales/fil/messages.json index 5835821f526..25dd12dd51e 100644 --- a/apps/desktop/src/locales/fil/messages.json +++ b/apps/desktop/src/locales/fil/messages.json @@ -1023,6 +1023,9 @@ "invalidVerificationCode": { "message": "Maling verification code" }, + "invalidEmailOrVerificationCode": { + "message": "Invalid email or verification code" + }, "continue": { "message": "Magpatuloy" }, @@ -4615,7 +4618,13 @@ "enterMultipleEmailsSeparatedByComma": { "message": "Enter multiple emails by separating with a comma." }, + "emailsRequiredChangeAccessType": { + "message": "Email verification requires at least one email address. To remove all emails, change the access type above." + }, "emailPlaceholder": { "message": "user@bitwarden.com , user@acme.com" + }, + "userVerificationFailed": { + "message": "User verification failed." } } diff --git a/apps/desktop/src/locales/fr/messages.json b/apps/desktop/src/locales/fr/messages.json index e8d07e28d2d..04e0725c9b4 100644 --- a/apps/desktop/src/locales/fr/messages.json +++ b/apps/desktop/src/locales/fr/messages.json @@ -1023,6 +1023,9 @@ "invalidVerificationCode": { "message": "Code de vérification invalide" }, + "invalidEmailOrVerificationCode": { + "message": "Invalid email or verification code" + }, "continue": { "message": "Continuer" }, @@ -4615,7 +4618,13 @@ "enterMultipleEmailsSeparatedByComma": { "message": "Enter multiple emails by separating with a comma." }, + "emailsRequiredChangeAccessType": { + "message": "Email verification requires at least one email address. To remove all emails, change the access type above." + }, "emailPlaceholder": { "message": "user@bitwarden.com , user@acme.com" + }, + "userVerificationFailed": { + "message": "User verification failed." } } diff --git a/apps/desktop/src/locales/gl/messages.json b/apps/desktop/src/locales/gl/messages.json index 6d0922bd680..3b80024392e 100644 --- a/apps/desktop/src/locales/gl/messages.json +++ b/apps/desktop/src/locales/gl/messages.json @@ -1023,6 +1023,9 @@ "invalidVerificationCode": { "message": "Invalid verification code" }, + "invalidEmailOrVerificationCode": { + "message": "Invalid email or verification code" + }, "continue": { "message": "Continue" }, @@ -4615,7 +4618,13 @@ "enterMultipleEmailsSeparatedByComma": { "message": "Enter multiple emails by separating with a comma." }, + "emailsRequiredChangeAccessType": { + "message": "Email verification requires at least one email address. To remove all emails, change the access type above." + }, "emailPlaceholder": { "message": "user@bitwarden.com , user@acme.com" + }, + "userVerificationFailed": { + "message": "User verification failed." } } diff --git a/apps/desktop/src/locales/he/messages.json b/apps/desktop/src/locales/he/messages.json index 763401ac6fe..66654aafbc6 100644 --- a/apps/desktop/src/locales/he/messages.json +++ b/apps/desktop/src/locales/he/messages.json @@ -1023,6 +1023,9 @@ "invalidVerificationCode": { "message": "קוד אימות שגוי" }, + "invalidEmailOrVerificationCode": { + "message": "Invalid email or verification code" + }, "continue": { "message": "המשך" }, @@ -4615,7 +4618,13 @@ "enterMultipleEmailsSeparatedByComma": { "message": "Enter multiple emails by separating with a comma." }, + "emailsRequiredChangeAccessType": { + "message": "Email verification requires at least one email address. To remove all emails, change the access type above." + }, "emailPlaceholder": { "message": "user@bitwarden.com , user@acme.com" + }, + "userVerificationFailed": { + "message": "User verification failed." } } diff --git a/apps/desktop/src/locales/hi/messages.json b/apps/desktop/src/locales/hi/messages.json index 33b69ac1519..d797b6319c0 100644 --- a/apps/desktop/src/locales/hi/messages.json +++ b/apps/desktop/src/locales/hi/messages.json @@ -1023,6 +1023,9 @@ "invalidVerificationCode": { "message": "Invalid verification code" }, + "invalidEmailOrVerificationCode": { + "message": "Invalid email or verification code" + }, "continue": { "message": "Continue" }, @@ -4615,7 +4618,13 @@ "enterMultipleEmailsSeparatedByComma": { "message": "Enter multiple emails by separating with a comma." }, + "emailsRequiredChangeAccessType": { + "message": "Email verification requires at least one email address. To remove all emails, change the access type above." + }, "emailPlaceholder": { "message": "user@bitwarden.com , user@acme.com" + }, + "userVerificationFailed": { + "message": "User verification failed." } } diff --git a/apps/desktop/src/locales/hr/messages.json b/apps/desktop/src/locales/hr/messages.json index 2dc081fa3c7..969a0df9cee 100644 --- a/apps/desktop/src/locales/hr/messages.json +++ b/apps/desktop/src/locales/hr/messages.json @@ -1023,6 +1023,9 @@ "invalidVerificationCode": { "message": "Nevažeći kôd za provjeru" }, + "invalidEmailOrVerificationCode": { + "message": "Invalid email or verification code" + }, "continue": { "message": "Nastavi" }, @@ -4615,7 +4618,13 @@ "enterMultipleEmailsSeparatedByComma": { "message": "Enter multiple emails by separating with a comma." }, + "emailsRequiredChangeAccessType": { + "message": "Email verification requires at least one email address. To remove all emails, change the access type above." + }, "emailPlaceholder": { "message": "user@bitwarden.com , user@acme.com" + }, + "userVerificationFailed": { + "message": "User verification failed." } } diff --git a/apps/desktop/src/locales/hu/messages.json b/apps/desktop/src/locales/hu/messages.json index 3ec097c2a7a..d73d746fded 100644 --- a/apps/desktop/src/locales/hu/messages.json +++ b/apps/desktop/src/locales/hu/messages.json @@ -1023,6 +1023,9 @@ "invalidVerificationCode": { "message": "Érvénytelen ellenőrző kód" }, + "invalidEmailOrVerificationCode": { + "message": "Az email cím vagy az ellenőrző kód érvénytelen." + }, "continue": { "message": "Folytatás" }, @@ -4615,7 +4618,13 @@ "enterMultipleEmailsSeparatedByComma": { "message": "Írjunk be több email címet vesszővel elválasztva." }, + "emailsRequiredChangeAccessType": { + "message": "Az email cím ellenőrzéshez legalább egy email cím szükséges. Az összes email cím eltávolításához módosítsuk a fenti hozzáférési típust." + }, "emailPlaceholder": { "message": "user@bitwarden.com , user@acme.com" + }, + "userVerificationFailed": { + "message": "A felhasználó ellenőrzése sikertelen volt." } } diff --git a/apps/desktop/src/locales/id/messages.json b/apps/desktop/src/locales/id/messages.json index 7648f4bb99b..f5c74c471de 100644 --- a/apps/desktop/src/locales/id/messages.json +++ b/apps/desktop/src/locales/id/messages.json @@ -1023,6 +1023,9 @@ "invalidVerificationCode": { "message": "Kode verifikasi tidak valid" }, + "invalidEmailOrVerificationCode": { + "message": "Invalid email or verification code" + }, "continue": { "message": "Lanjutkan" }, @@ -4615,7 +4618,13 @@ "enterMultipleEmailsSeparatedByComma": { "message": "Enter multiple emails by separating with a comma." }, + "emailsRequiredChangeAccessType": { + "message": "Email verification requires at least one email address. To remove all emails, change the access type above." + }, "emailPlaceholder": { "message": "user@bitwarden.com , user@acme.com" + }, + "userVerificationFailed": { + "message": "User verification failed." } } diff --git a/apps/desktop/src/locales/it/messages.json b/apps/desktop/src/locales/it/messages.json index eb2ade245a0..ba1bb9e5346 100644 --- a/apps/desktop/src/locales/it/messages.json +++ b/apps/desktop/src/locales/it/messages.json @@ -1023,6 +1023,9 @@ "invalidVerificationCode": { "message": "Codice di verifica non valido" }, + "invalidEmailOrVerificationCode": { + "message": "Codice di verifica non valido" + }, "continue": { "message": "Continua" }, @@ -4388,10 +4391,10 @@ "message": "Gli elementi archiviati appariranno qui e saranno esclusi dai risultati di ricerca e dal riempimento automatico." }, "itemArchiveToast": { - "message": "Item archived" + "message": "Elemento archiviato" }, "itemUnarchivedToast": { - "message": "Item unarchived" + "message": "Elemento estratto dall'archivio" }, "archiveItem": { "message": "Archivia elemento" @@ -4487,7 +4490,7 @@ "message": "Azione al timeout" }, "errorCannotDecrypt": { - "message": "Error: Cannot decrypt" + "message": "Errore: impossibile decrittare" }, "sessionTimeoutHeader": { "message": "Timeout della sessione" @@ -4588,34 +4591,40 @@ "message": "Perché vedo questo avviso?" }, "sendPasswordHelperText": { - "message": "Individuals will need to enter the password to view this Send", + "message": "I destinatari dovranno inserire la password per visualizzare questo Send", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "emailProtected": { - "message": "Email protected" + "message": "Email protetta" }, "emails": { - "message": "Emails" + "message": "Indirizzi email" }, "noAuth": { - "message": "Anyone with the link" + "message": "Chiunque abbia il link" }, "anyOneWithPassword": { - "message": "Anyone with a password set by you" + "message": "Chiunque abbia una password impostata da te" }, "whoCanView": { - "message": "Who can view" + "message": "Chi può visualizzare" }, "specificPeople": { - "message": "Specific people" + "message": "Persone specifiche" }, "emailVerificationDesc": { - "message": "After sharing this Send link, individuals will need to verify their email with a code to view this Send." + "message": "I destinatari dovranno verificare il loro indirizzo email con un codice per poter visualizzare il Send." }, "enterMultipleEmailsSeparatedByComma": { - "message": "Enter multiple emails by separating with a comma." + "message": "Inserisci più indirizzi email separandoli con virgole." + }, + "emailsRequiredChangeAccessType": { + "message": "La verifica via email richiede almeno un indirizzo email. Per rimuovere tutte le email, modifica il tipo di accesso qui sopra." }, "emailPlaceholder": { "message": "user@bitwarden.com , user@acme.com" + }, + "userVerificationFailed": { + "message": "Verifica dell'utente non riuscita." } } diff --git a/apps/desktop/src/locales/ja/messages.json b/apps/desktop/src/locales/ja/messages.json index a9b05f728d8..908fa271a16 100644 --- a/apps/desktop/src/locales/ja/messages.json +++ b/apps/desktop/src/locales/ja/messages.json @@ -1023,6 +1023,9 @@ "invalidVerificationCode": { "message": "認証コードが間違っています" }, + "invalidEmailOrVerificationCode": { + "message": "Invalid email or verification code" + }, "continue": { "message": "続行" }, @@ -4615,7 +4618,13 @@ "enterMultipleEmailsSeparatedByComma": { "message": "Enter multiple emails by separating with a comma." }, + "emailsRequiredChangeAccessType": { + "message": "Email verification requires at least one email address. To remove all emails, change the access type above." + }, "emailPlaceholder": { "message": "user@bitwarden.com , user@acme.com" + }, + "userVerificationFailed": { + "message": "User verification failed." } } diff --git a/apps/desktop/src/locales/ka/messages.json b/apps/desktop/src/locales/ka/messages.json index 68bba7fcb27..b9fb3f528d7 100644 --- a/apps/desktop/src/locales/ka/messages.json +++ b/apps/desktop/src/locales/ka/messages.json @@ -1023,6 +1023,9 @@ "invalidVerificationCode": { "message": "არასწორი გადამოწმების კოდი" }, + "invalidEmailOrVerificationCode": { + "message": "Invalid email or verification code" + }, "continue": { "message": "გაგრძელება" }, @@ -4615,7 +4618,13 @@ "enterMultipleEmailsSeparatedByComma": { "message": "Enter multiple emails by separating with a comma." }, + "emailsRequiredChangeAccessType": { + "message": "Email verification requires at least one email address. To remove all emails, change the access type above." + }, "emailPlaceholder": { "message": "user@bitwarden.com , user@acme.com" + }, + "userVerificationFailed": { + "message": "User verification failed." } } diff --git a/apps/desktop/src/locales/km/messages.json b/apps/desktop/src/locales/km/messages.json index 6d0922bd680..3b80024392e 100644 --- a/apps/desktop/src/locales/km/messages.json +++ b/apps/desktop/src/locales/km/messages.json @@ -1023,6 +1023,9 @@ "invalidVerificationCode": { "message": "Invalid verification code" }, + "invalidEmailOrVerificationCode": { + "message": "Invalid email or verification code" + }, "continue": { "message": "Continue" }, @@ -4615,7 +4618,13 @@ "enterMultipleEmailsSeparatedByComma": { "message": "Enter multiple emails by separating with a comma." }, + "emailsRequiredChangeAccessType": { + "message": "Email verification requires at least one email address. To remove all emails, change the access type above." + }, "emailPlaceholder": { "message": "user@bitwarden.com , user@acme.com" + }, + "userVerificationFailed": { + "message": "User verification failed." } } diff --git a/apps/desktop/src/locales/kn/messages.json b/apps/desktop/src/locales/kn/messages.json index 3c6aced3a73..13463f63da1 100644 --- a/apps/desktop/src/locales/kn/messages.json +++ b/apps/desktop/src/locales/kn/messages.json @@ -1023,6 +1023,9 @@ "invalidVerificationCode": { "message": "Invalid verification code" }, + "invalidEmailOrVerificationCode": { + "message": "Invalid email or verification code" + }, "continue": { "message": "ಮುಂದುವರಿಸಿ" }, @@ -4615,7 +4618,13 @@ "enterMultipleEmailsSeparatedByComma": { "message": "Enter multiple emails by separating with a comma." }, + "emailsRequiredChangeAccessType": { + "message": "Email verification requires at least one email address. To remove all emails, change the access type above." + }, "emailPlaceholder": { "message": "user@bitwarden.com , user@acme.com" + }, + "userVerificationFailed": { + "message": "User verification failed." } } diff --git a/apps/desktop/src/locales/ko/messages.json b/apps/desktop/src/locales/ko/messages.json index 8932f0efb48..524d1bc8b01 100644 --- a/apps/desktop/src/locales/ko/messages.json +++ b/apps/desktop/src/locales/ko/messages.json @@ -1023,6 +1023,9 @@ "invalidVerificationCode": { "message": "유효하지 않은 확인 코드" }, + "invalidEmailOrVerificationCode": { + "message": "Invalid email or verification code" + }, "continue": { "message": "계속" }, @@ -4615,7 +4618,13 @@ "enterMultipleEmailsSeparatedByComma": { "message": "Enter multiple emails by separating with a comma." }, + "emailsRequiredChangeAccessType": { + "message": "Email verification requires at least one email address. To remove all emails, change the access type above." + }, "emailPlaceholder": { "message": "user@bitwarden.com , user@acme.com" + }, + "userVerificationFailed": { + "message": "User verification failed." } } diff --git a/apps/desktop/src/locales/lt/messages.json b/apps/desktop/src/locales/lt/messages.json index a19856a776e..3a003cb565e 100644 --- a/apps/desktop/src/locales/lt/messages.json +++ b/apps/desktop/src/locales/lt/messages.json @@ -1023,6 +1023,9 @@ "invalidVerificationCode": { "message": "Neteisingas patvirtinimo kodas" }, + "invalidEmailOrVerificationCode": { + "message": "Invalid email or verification code" + }, "continue": { "message": "Tęsti" }, @@ -4615,7 +4618,13 @@ "enterMultipleEmailsSeparatedByComma": { "message": "Enter multiple emails by separating with a comma." }, + "emailsRequiredChangeAccessType": { + "message": "Email verification requires at least one email address. To remove all emails, change the access type above." + }, "emailPlaceholder": { "message": "user@bitwarden.com , user@acme.com" + }, + "userVerificationFailed": { + "message": "User verification failed." } } diff --git a/apps/desktop/src/locales/lv/messages.json b/apps/desktop/src/locales/lv/messages.json index 8a863256ed1..b8a0cdd8434 100644 --- a/apps/desktop/src/locales/lv/messages.json +++ b/apps/desktop/src/locales/lv/messages.json @@ -1023,6 +1023,9 @@ "invalidVerificationCode": { "message": "Nederīgs apliecinājuma kods" }, + "invalidEmailOrVerificationCode": { + "message": "Nederīga e-pasta adrese vai apliecinājuma kods" + }, "continue": { "message": "Turpināt" }, @@ -4388,10 +4391,10 @@ "message": "Šeit parādīsies arhivētie vienumi, un tie netiks iekļauti vispārējās meklēšanas iznākumos un automātiskās aizpildes ieteikumos." }, "itemArchiveToast": { - "message": "Item archived" + "message": "Vienums ievietots arhīvā" }, "itemUnarchivedToast": { - "message": "Item unarchived" + "message": "Vienums izņemts no arhīva" }, "archiveItem": { "message": "Arhivēt vienumu" @@ -4615,7 +4618,13 @@ "enterMultipleEmailsSeparatedByComma": { "message": "Enter multiple emails by separating with a comma." }, + "emailsRequiredChangeAccessType": { + "message": "E-pasta apliecināšanai ir nepieciešama vismaz viena e-pasta adrese. Lai noņemtu visas e-pasta adreses, augstāk jānomaina piekļūšanas veids." + }, "emailPlaceholder": { "message": "user@bitwarden.com , user@acme.com" + }, + "userVerificationFailed": { + "message": "Lietotāja apliecināšana neizdevās." } } diff --git a/apps/desktop/src/locales/me/messages.json b/apps/desktop/src/locales/me/messages.json index 773a596a10d..de1c690bb8e 100644 --- a/apps/desktop/src/locales/me/messages.json +++ b/apps/desktop/src/locales/me/messages.json @@ -1023,6 +1023,9 @@ "invalidVerificationCode": { "message": "Invalid verification code" }, + "invalidEmailOrVerificationCode": { + "message": "Invalid email or verification code" + }, "continue": { "message": "Nastavi" }, @@ -4615,7 +4618,13 @@ "enterMultipleEmailsSeparatedByComma": { "message": "Enter multiple emails by separating with a comma." }, + "emailsRequiredChangeAccessType": { + "message": "Email verification requires at least one email address. To remove all emails, change the access type above." + }, "emailPlaceholder": { "message": "user@bitwarden.com , user@acme.com" + }, + "userVerificationFailed": { + "message": "User verification failed." } } diff --git a/apps/desktop/src/locales/ml/messages.json b/apps/desktop/src/locales/ml/messages.json index 3bddc3baa5b..0b15dafd31b 100644 --- a/apps/desktop/src/locales/ml/messages.json +++ b/apps/desktop/src/locales/ml/messages.json @@ -1023,6 +1023,9 @@ "invalidVerificationCode": { "message": "Invalid verification code" }, + "invalidEmailOrVerificationCode": { + "message": "Invalid email or verification code" + }, "continue": { "message": "തുടരുക" }, @@ -4615,7 +4618,13 @@ "enterMultipleEmailsSeparatedByComma": { "message": "Enter multiple emails by separating with a comma." }, + "emailsRequiredChangeAccessType": { + "message": "Email verification requires at least one email address. To remove all emails, change the access type above." + }, "emailPlaceholder": { "message": "user@bitwarden.com , user@acme.com" + }, + "userVerificationFailed": { + "message": "User verification failed." } } diff --git a/apps/desktop/src/locales/mr/messages.json b/apps/desktop/src/locales/mr/messages.json index 6d0922bd680..3b80024392e 100644 --- a/apps/desktop/src/locales/mr/messages.json +++ b/apps/desktop/src/locales/mr/messages.json @@ -1023,6 +1023,9 @@ "invalidVerificationCode": { "message": "Invalid verification code" }, + "invalidEmailOrVerificationCode": { + "message": "Invalid email or verification code" + }, "continue": { "message": "Continue" }, @@ -4615,7 +4618,13 @@ "enterMultipleEmailsSeparatedByComma": { "message": "Enter multiple emails by separating with a comma." }, + "emailsRequiredChangeAccessType": { + "message": "Email verification requires at least one email address. To remove all emails, change the access type above." + }, "emailPlaceholder": { "message": "user@bitwarden.com , user@acme.com" + }, + "userVerificationFailed": { + "message": "User verification failed." } } diff --git a/apps/desktop/src/locales/my/messages.json b/apps/desktop/src/locales/my/messages.json index 22f4a30329a..6efe4072ecb 100644 --- a/apps/desktop/src/locales/my/messages.json +++ b/apps/desktop/src/locales/my/messages.json @@ -1023,6 +1023,9 @@ "invalidVerificationCode": { "message": "Invalid verification code" }, + "invalidEmailOrVerificationCode": { + "message": "Invalid email or verification code" + }, "continue": { "message": "Continue" }, @@ -4615,7 +4618,13 @@ "enterMultipleEmailsSeparatedByComma": { "message": "Enter multiple emails by separating with a comma." }, + "emailsRequiredChangeAccessType": { + "message": "Email verification requires at least one email address. To remove all emails, change the access type above." + }, "emailPlaceholder": { "message": "user@bitwarden.com , user@acme.com" + }, + "userVerificationFailed": { + "message": "User verification failed." } } diff --git a/apps/desktop/src/locales/nb/messages.json b/apps/desktop/src/locales/nb/messages.json index b2b1631fe04..a4842046c16 100644 --- a/apps/desktop/src/locales/nb/messages.json +++ b/apps/desktop/src/locales/nb/messages.json @@ -1023,6 +1023,9 @@ "invalidVerificationCode": { "message": "Ugyldig verifiseringskode" }, + "invalidEmailOrVerificationCode": { + "message": "Invalid email or verification code" + }, "continue": { "message": "Fortsett" }, @@ -4615,7 +4618,13 @@ "enterMultipleEmailsSeparatedByComma": { "message": "Enter multiple emails by separating with a comma." }, + "emailsRequiredChangeAccessType": { + "message": "Email verification requires at least one email address. To remove all emails, change the access type above." + }, "emailPlaceholder": { "message": "user@bitwarden.com , user@acme.com" + }, + "userVerificationFailed": { + "message": "User verification failed." } } diff --git a/apps/desktop/src/locales/ne/messages.json b/apps/desktop/src/locales/ne/messages.json index b9eba55d8bd..43a96999ed3 100644 --- a/apps/desktop/src/locales/ne/messages.json +++ b/apps/desktop/src/locales/ne/messages.json @@ -1023,6 +1023,9 @@ "invalidVerificationCode": { "message": "Invalid verification code" }, + "invalidEmailOrVerificationCode": { + "message": "Invalid email or verification code" + }, "continue": { "message": "Continue" }, @@ -4615,7 +4618,13 @@ "enterMultipleEmailsSeparatedByComma": { "message": "Enter multiple emails by separating with a comma." }, + "emailsRequiredChangeAccessType": { + "message": "Email verification requires at least one email address. To remove all emails, change the access type above." + }, "emailPlaceholder": { "message": "user@bitwarden.com , user@acme.com" + }, + "userVerificationFailed": { + "message": "User verification failed." } } diff --git a/apps/desktop/src/locales/nl/messages.json b/apps/desktop/src/locales/nl/messages.json index 306bf31efe2..f6908fd6498 100644 --- a/apps/desktop/src/locales/nl/messages.json +++ b/apps/desktop/src/locales/nl/messages.json @@ -1023,6 +1023,9 @@ "invalidVerificationCode": { "message": "Ongeldige verificatiecode" }, + "invalidEmailOrVerificationCode": { + "message": "Ongeldig e-mailadres verificatiecode" + }, "continue": { "message": "Doorgaan" }, @@ -4615,7 +4618,13 @@ "enterMultipleEmailsSeparatedByComma": { "message": "Voer meerdere e-mailadressen in door te scheiden met een komma." }, + "emailsRequiredChangeAccessType": { + "message": "E-mailverificatie vereist ten minste één e-mailadres. Om alle e-mailadressen te verwijderen, moet je het toegangstype hierboven wijzigen." + }, "emailPlaceholder": { "message": "user@bitwarden.com , user@acme.com" + }, + "userVerificationFailed": { + "message": "Gebruikersverificatie is mislukt." } } diff --git a/apps/desktop/src/locales/nn/messages.json b/apps/desktop/src/locales/nn/messages.json index dc62d73a236..1c0ab7c51a8 100644 --- a/apps/desktop/src/locales/nn/messages.json +++ b/apps/desktop/src/locales/nn/messages.json @@ -1023,6 +1023,9 @@ "invalidVerificationCode": { "message": "Ugyldig stadfestingskode" }, + "invalidEmailOrVerificationCode": { + "message": "Invalid email or verification code" + }, "continue": { "message": "Fortsett" }, @@ -4615,7 +4618,13 @@ "enterMultipleEmailsSeparatedByComma": { "message": "Enter multiple emails by separating with a comma." }, + "emailsRequiredChangeAccessType": { + "message": "Email verification requires at least one email address. To remove all emails, change the access type above." + }, "emailPlaceholder": { "message": "user@bitwarden.com , user@acme.com" + }, + "userVerificationFailed": { + "message": "User verification failed." } } diff --git a/apps/desktop/src/locales/or/messages.json b/apps/desktop/src/locales/or/messages.json index e8ea506a873..f28ba8ad5a0 100644 --- a/apps/desktop/src/locales/or/messages.json +++ b/apps/desktop/src/locales/or/messages.json @@ -1023,6 +1023,9 @@ "invalidVerificationCode": { "message": "Invalid verification code" }, + "invalidEmailOrVerificationCode": { + "message": "Invalid email or verification code" + }, "continue": { "message": "Continue" }, @@ -4615,7 +4618,13 @@ "enterMultipleEmailsSeparatedByComma": { "message": "Enter multiple emails by separating with a comma." }, + "emailsRequiredChangeAccessType": { + "message": "Email verification requires at least one email address. To remove all emails, change the access type above." + }, "emailPlaceholder": { "message": "user@bitwarden.com , user@acme.com" + }, + "userVerificationFailed": { + "message": "User verification failed." } } diff --git a/apps/desktop/src/locales/pl/messages.json b/apps/desktop/src/locales/pl/messages.json index 0cee8d6683d..e429217c278 100644 --- a/apps/desktop/src/locales/pl/messages.json +++ b/apps/desktop/src/locales/pl/messages.json @@ -1023,6 +1023,9 @@ "invalidVerificationCode": { "message": "Kod weryfikacyjny jest nieprawidłowy" }, + "invalidEmailOrVerificationCode": { + "message": "Invalid email or verification code" + }, "continue": { "message": "Kontynuuj" }, @@ -4615,7 +4618,13 @@ "enterMultipleEmailsSeparatedByComma": { "message": "Enter multiple emails by separating with a comma." }, + "emailsRequiredChangeAccessType": { + "message": "Email verification requires at least one email address. To remove all emails, change the access type above." + }, "emailPlaceholder": { "message": "user@bitwarden.com , user@acme.com" + }, + "userVerificationFailed": { + "message": "User verification failed." } } diff --git a/apps/desktop/src/locales/pt_BR/messages.json b/apps/desktop/src/locales/pt_BR/messages.json index 817e9de0c50..1ec4807e2a4 100644 --- a/apps/desktop/src/locales/pt_BR/messages.json +++ b/apps/desktop/src/locales/pt_BR/messages.json @@ -1023,6 +1023,9 @@ "invalidVerificationCode": { "message": "Código de verificação inválido" }, + "invalidEmailOrVerificationCode": { + "message": "E-mail ou código de verificação inválido" + }, "continue": { "message": "Continuar" }, @@ -4388,10 +4391,10 @@ "message": "Os itens arquivados aparecerão aqui e serão excluídos dos resultados gerais de busca e das sugestões de preenchimento automático." }, "itemArchiveToast": { - "message": "Item archived" + "message": "Item arquivado" }, "itemUnarchivedToast": { - "message": "Item unarchived" + "message": "Item desarquivado" }, "archiveItem": { "message": "Arquivar item" @@ -4487,7 +4490,7 @@ "message": "Ação do limite de tempo" }, "errorCannotDecrypt": { - "message": "Error: Cannot decrypt" + "message": "Erro: Não é possível descriptografar" }, "sessionTimeoutHeader": { "message": "Limite de tempo da sessão" @@ -4588,11 +4591,11 @@ "message": "Por que estou vendo isso?" }, "sendPasswordHelperText": { - "message": "Indivíduos precisarão utilizar a senha para ver este Send", + "message": "Os indivíduos precisarão digitar a senha para ver este Send", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "emailProtected": { - "message": "E-mail protegido" + "message": "Protegido por e-mail" }, "emails": { "message": "E-mails" @@ -4601,7 +4604,7 @@ "message": "Qualquer um com o link" }, "anyOneWithPassword": { - "message": "Qualquer um com uma senha definida por você" + "message": "Qualquer pessoa com uma senha configurada por você" }, "whoCanView": { "message": "Quem pode visualizar" @@ -4613,9 +4616,15 @@ "message": "Após compartilhar este link de Send, indivíduos precisarão verificar seus e-mails com um código para visualizar este Send." }, "enterMultipleEmailsSeparatedByComma": { - "message": "Insira múltiplos e-mails separando-os com vírgula." + "message": "Digite vários e-mails, separados com uma vírgula." + }, + "emailsRequiredChangeAccessType": { + "message": "A verificação de e-mail requer pelo menos um endereço de e-mail. Para remover todos, altere o tipo de acesso acima." }, "emailPlaceholder": { - "message": "user@bitwarden.com , user@acme.com" + "message": "usuário@bitwarden.com , usuário@acme.com" + }, + "userVerificationFailed": { + "message": "Falha na verificação do usuário." } } diff --git a/apps/desktop/src/locales/pt_PT/messages.json b/apps/desktop/src/locales/pt_PT/messages.json index 3076291a4a3..ca5091ccbe6 100644 --- a/apps/desktop/src/locales/pt_PT/messages.json +++ b/apps/desktop/src/locales/pt_PT/messages.json @@ -1023,6 +1023,9 @@ "invalidVerificationCode": { "message": "Código de verificação inválido" }, + "invalidEmailOrVerificationCode": { + "message": "E-mail ou código de verificação inválido" + }, "continue": { "message": "Continuar" }, @@ -4615,7 +4618,13 @@ "enterMultipleEmailsSeparatedByComma": { "message": "Introduza vários e-mails, separados por vírgula." }, + "emailsRequiredChangeAccessType": { + "message": "A verificação por e-mail requer pelo menos um endereço de e-mail. Para remover todos os e-mails, altere o tipo de acesso acima." + }, "emailPlaceholder": { "message": "utilizador@bitwarden.com , utilizador@acme.com" + }, + "userVerificationFailed": { + "message": "Falha na verificação do utilizador." } } diff --git a/apps/desktop/src/locales/ro/messages.json b/apps/desktop/src/locales/ro/messages.json index b8fc25b4105..6c67e9d9e06 100644 --- a/apps/desktop/src/locales/ro/messages.json +++ b/apps/desktop/src/locales/ro/messages.json @@ -1023,6 +1023,9 @@ "invalidVerificationCode": { "message": "Cod de verificare nevalid" }, + "invalidEmailOrVerificationCode": { + "message": "Invalid email or verification code" + }, "continue": { "message": "Continuare" }, @@ -4615,7 +4618,13 @@ "enterMultipleEmailsSeparatedByComma": { "message": "Enter multiple emails by separating with a comma." }, + "emailsRequiredChangeAccessType": { + "message": "Email verification requires at least one email address. To remove all emails, change the access type above." + }, "emailPlaceholder": { "message": "user@bitwarden.com , user@acme.com" + }, + "userVerificationFailed": { + "message": "User verification failed." } } diff --git a/apps/desktop/src/locales/ru/messages.json b/apps/desktop/src/locales/ru/messages.json index 089e815fefe..e48319dac55 100644 --- a/apps/desktop/src/locales/ru/messages.json +++ b/apps/desktop/src/locales/ru/messages.json @@ -1023,6 +1023,9 @@ "invalidVerificationCode": { "message": "Неверный код подтверждения" }, + "invalidEmailOrVerificationCode": { + "message": "Неверный email или код подтверждения" + }, "continue": { "message": "Продолжить" }, @@ -4615,7 +4618,13 @@ "enterMultipleEmailsSeparatedByComma": { "message": "Введите несколько email, разделяя их запятой." }, + "emailsRequiredChangeAccessType": { + "message": "Для проверки электронной почты требуется как минимум один адрес email. Чтобы удалить все адреса электронной почты, измените тип доступа выше." + }, "emailPlaceholder": { "message": "user@bitwarden.com , user@acme.com" + }, + "userVerificationFailed": { + "message": "Проверка пользователя не удалась." } } diff --git a/apps/desktop/src/locales/si/messages.json b/apps/desktop/src/locales/si/messages.json index a1a84b8ba15..4d75c329656 100644 --- a/apps/desktop/src/locales/si/messages.json +++ b/apps/desktop/src/locales/si/messages.json @@ -1023,6 +1023,9 @@ "invalidVerificationCode": { "message": "Invalid verification code" }, + "invalidEmailOrVerificationCode": { + "message": "Invalid email or verification code" + }, "continue": { "message": "ඉදිරියට" }, @@ -4615,7 +4618,13 @@ "enterMultipleEmailsSeparatedByComma": { "message": "Enter multiple emails by separating with a comma." }, + "emailsRequiredChangeAccessType": { + "message": "Email verification requires at least one email address. To remove all emails, change the access type above." + }, "emailPlaceholder": { "message": "user@bitwarden.com , user@acme.com" + }, + "userVerificationFailed": { + "message": "User verification failed." } } diff --git a/apps/desktop/src/locales/sk/messages.json b/apps/desktop/src/locales/sk/messages.json index 2876361bbf0..50d88a49405 100644 --- a/apps/desktop/src/locales/sk/messages.json +++ b/apps/desktop/src/locales/sk/messages.json @@ -1023,6 +1023,9 @@ "invalidVerificationCode": { "message": "Neplatný verifikačný kód" }, + "invalidEmailOrVerificationCode": { + "message": "Invalid email or verification code" + }, "continue": { "message": "Pokračovať" }, @@ -4615,7 +4618,13 @@ "enterMultipleEmailsSeparatedByComma": { "message": "Zadajte viacero e-mailových adries oddelených čiarkou." }, + "emailsRequiredChangeAccessType": { + "message": "Overenie e-mailu vyžaduje aspoň jednu e-mailovú adresu. Ak chcete odstrániť všetky e-maily, zmeňte typ prístupu vyššie." + }, "emailPlaceholder": { "message": "pouzivate@bitwarden.com, pouzivatel@acme.com" + }, + "userVerificationFailed": { + "message": "Overenie používateľa zlyhalo." } } diff --git a/apps/desktop/src/locales/sl/messages.json b/apps/desktop/src/locales/sl/messages.json index 82e0a20b29e..63ae05a12b5 100644 --- a/apps/desktop/src/locales/sl/messages.json +++ b/apps/desktop/src/locales/sl/messages.json @@ -1023,6 +1023,9 @@ "invalidVerificationCode": { "message": "Neveljavna verifikacijska koda" }, + "invalidEmailOrVerificationCode": { + "message": "Invalid email or verification code" + }, "continue": { "message": "Nadaljuj" }, @@ -4615,7 +4618,13 @@ "enterMultipleEmailsSeparatedByComma": { "message": "Enter multiple emails by separating with a comma." }, + "emailsRequiredChangeAccessType": { + "message": "Email verification requires at least one email address. To remove all emails, change the access type above." + }, "emailPlaceholder": { "message": "user@bitwarden.com , user@acme.com" + }, + "userVerificationFailed": { + "message": "User verification failed." } } diff --git a/apps/desktop/src/locales/sr/messages.json b/apps/desktop/src/locales/sr/messages.json index 633d3123242..32715ed60d1 100644 --- a/apps/desktop/src/locales/sr/messages.json +++ b/apps/desktop/src/locales/sr/messages.json @@ -1023,6 +1023,9 @@ "invalidVerificationCode": { "message": "Неисправан верификациони код" }, + "invalidEmailOrVerificationCode": { + "message": "Invalid email or verification code" + }, "continue": { "message": "Настави" }, @@ -4615,7 +4618,13 @@ "enterMultipleEmailsSeparatedByComma": { "message": "Enter multiple emails by separating with a comma." }, + "emailsRequiredChangeAccessType": { + "message": "Email verification requires at least one email address. To remove all emails, change the access type above." + }, "emailPlaceholder": { "message": "user@bitwarden.com , user@acme.com" + }, + "userVerificationFailed": { + "message": "User verification failed." } } diff --git a/apps/desktop/src/locales/sv/messages.json b/apps/desktop/src/locales/sv/messages.json index ed9e62e8319..b44d54a14a3 100644 --- a/apps/desktop/src/locales/sv/messages.json +++ b/apps/desktop/src/locales/sv/messages.json @@ -1023,6 +1023,9 @@ "invalidVerificationCode": { "message": "Ogiltig verifieringskod" }, + "invalidEmailOrVerificationCode": { + "message": "Invalid email or verification code" + }, "continue": { "message": "Fortsätt" }, @@ -4615,7 +4618,13 @@ "enterMultipleEmailsSeparatedByComma": { "message": "Ange flera e-postadresser genom att separera dem med kommatecken." }, + "emailsRequiredChangeAccessType": { + "message": "Email verification requires at least one email address. To remove all emails, change the access type above." + }, "emailPlaceholder": { "message": "användare@bitwarden.com , användare@acme.com" + }, + "userVerificationFailed": { + "message": "Verifiering av användare misslyckades." } } diff --git a/apps/desktop/src/locales/ta/messages.json b/apps/desktop/src/locales/ta/messages.json index 53e155874f6..feb4b92c77e 100644 --- a/apps/desktop/src/locales/ta/messages.json +++ b/apps/desktop/src/locales/ta/messages.json @@ -1023,6 +1023,9 @@ "invalidVerificationCode": { "message": "தவறான சரிபார்ப்புக் குறியீடு" }, + "invalidEmailOrVerificationCode": { + "message": "Invalid email or verification code" + }, "continue": { "message": "தொடரவும்" }, @@ -4615,7 +4618,13 @@ "enterMultipleEmailsSeparatedByComma": { "message": "Enter multiple emails by separating with a comma." }, + "emailsRequiredChangeAccessType": { + "message": "Email verification requires at least one email address. To remove all emails, change the access type above." + }, "emailPlaceholder": { "message": "user@bitwarden.com , user@acme.com" + }, + "userVerificationFailed": { + "message": "User verification failed." } } diff --git a/apps/desktop/src/locales/te/messages.json b/apps/desktop/src/locales/te/messages.json index 6d0922bd680..3b80024392e 100644 --- a/apps/desktop/src/locales/te/messages.json +++ b/apps/desktop/src/locales/te/messages.json @@ -1023,6 +1023,9 @@ "invalidVerificationCode": { "message": "Invalid verification code" }, + "invalidEmailOrVerificationCode": { + "message": "Invalid email or verification code" + }, "continue": { "message": "Continue" }, @@ -4615,7 +4618,13 @@ "enterMultipleEmailsSeparatedByComma": { "message": "Enter multiple emails by separating with a comma." }, + "emailsRequiredChangeAccessType": { + "message": "Email verification requires at least one email address. To remove all emails, change the access type above." + }, "emailPlaceholder": { "message": "user@bitwarden.com , user@acme.com" + }, + "userVerificationFailed": { + "message": "User verification failed." } } diff --git a/apps/desktop/src/locales/th/messages.json b/apps/desktop/src/locales/th/messages.json index b7117a7ccad..eeb0029b928 100644 --- a/apps/desktop/src/locales/th/messages.json +++ b/apps/desktop/src/locales/th/messages.json @@ -1023,6 +1023,9 @@ "invalidVerificationCode": { "message": "รหัสการตรวจสอบสิทธิ์ไม่ถูกต้อง" }, + "invalidEmailOrVerificationCode": { + "message": "Invalid email or verification code" + }, "continue": { "message": "ดำเนินการต่อไป" }, @@ -4615,7 +4618,13 @@ "enterMultipleEmailsSeparatedByComma": { "message": "Enter multiple emails by separating with a comma." }, + "emailsRequiredChangeAccessType": { + "message": "Email verification requires at least one email address. To remove all emails, change the access type above." + }, "emailPlaceholder": { "message": "user@bitwarden.com , user@acme.com" + }, + "userVerificationFailed": { + "message": "User verification failed." } } diff --git a/apps/desktop/src/locales/tr/messages.json b/apps/desktop/src/locales/tr/messages.json index 96bb11c7a35..29171ca8fef 100644 --- a/apps/desktop/src/locales/tr/messages.json +++ b/apps/desktop/src/locales/tr/messages.json @@ -1023,6 +1023,9 @@ "invalidVerificationCode": { "message": "Geçersiz doğrulama kodu" }, + "invalidEmailOrVerificationCode": { + "message": "E-posta veya doğrulama kodu geçersiz" + }, "continue": { "message": "Devam Et" }, @@ -4615,7 +4618,13 @@ "enterMultipleEmailsSeparatedByComma": { "message": "E-posta adreslerini virgülle ayırarak yazın." }, + "emailsRequiredChangeAccessType": { + "message": "E-posta doğrulaması için en az bir e-posta adresi gerekir. Tüm e-postaları silmek için yukarıdan erişim türünü değiştirin." + }, "emailPlaceholder": { "message": "kullanici@bitwarden.com , kullanici@acme.com" + }, + "userVerificationFailed": { + "message": "Kullanıcı doğrulaması başarısız oldu." } } diff --git a/apps/desktop/src/locales/uk/messages.json b/apps/desktop/src/locales/uk/messages.json index abcdbea0b1f..e982d46b454 100644 --- a/apps/desktop/src/locales/uk/messages.json +++ b/apps/desktop/src/locales/uk/messages.json @@ -1023,6 +1023,9 @@ "invalidVerificationCode": { "message": "Недійсний код підтвердження" }, + "invalidEmailOrVerificationCode": { + "message": "Invalid email or verification code" + }, "continue": { "message": "Продовжити" }, @@ -4615,7 +4618,13 @@ "enterMultipleEmailsSeparatedByComma": { "message": "Enter multiple emails by separating with a comma." }, + "emailsRequiredChangeAccessType": { + "message": "Email verification requires at least one email address. To remove all emails, change the access type above." + }, "emailPlaceholder": { "message": "user@bitwarden.com , user@acme.com" + }, + "userVerificationFailed": { + "message": "User verification failed." } } diff --git a/apps/desktop/src/locales/vi/messages.json b/apps/desktop/src/locales/vi/messages.json index f06556568a1..8fe0adee948 100644 --- a/apps/desktop/src/locales/vi/messages.json +++ b/apps/desktop/src/locales/vi/messages.json @@ -1023,6 +1023,9 @@ "invalidVerificationCode": { "message": "Mã xác minh không đúng" }, + "invalidEmailOrVerificationCode": { + "message": "Invalid email or verification code" + }, "continue": { "message": "Tiếp tục" }, @@ -4615,7 +4618,13 @@ "enterMultipleEmailsSeparatedByComma": { "message": "Enter multiple emails by separating with a comma." }, + "emailsRequiredChangeAccessType": { + "message": "Email verification requires at least one email address. To remove all emails, change the access type above." + }, "emailPlaceholder": { "message": "user@bitwarden.com , user@acme.com" + }, + "userVerificationFailed": { + "message": "User verification failed." } } diff --git a/apps/desktop/src/locales/zh_CN/messages.json b/apps/desktop/src/locales/zh_CN/messages.json index 9941e296da3..bd3a897ffab 100644 --- a/apps/desktop/src/locales/zh_CN/messages.json +++ b/apps/desktop/src/locales/zh_CN/messages.json @@ -1023,6 +1023,9 @@ "invalidVerificationCode": { "message": "无效的验证码" }, + "invalidEmailOrVerificationCode": { + "message": "无效的电子邮箱或验证码" + }, "continue": { "message": "继续" }, @@ -4388,10 +4391,10 @@ "message": "已归档的项目将显示在此处,并将被排除在一般搜索结果和自动填充建议之外。" }, "itemArchiveToast": { - "message": "Item archived" + "message": "项目已归档" }, "itemUnarchivedToast": { - "message": "Item unarchived" + "message": "项目已取消归档" }, "archiveItem": { "message": "归档项目" @@ -4615,7 +4618,13 @@ "enterMultipleEmailsSeparatedByComma": { "message": "输入多个电子邮箱(使用逗号分隔)。" }, + "emailsRequiredChangeAccessType": { + "message": "电子邮箱验证要求至少有一个电子邮箱地址。要移除所有电子邮箱,请更改上面的访问类型。" + }, "emailPlaceholder": { "message": "user@bitwarden.com, user@acme.com" + }, + "userVerificationFailed": { + "message": "用户验证失败。" } } diff --git a/apps/desktop/src/locales/zh_TW/messages.json b/apps/desktop/src/locales/zh_TW/messages.json index 461eb031068..364f67c7b58 100644 --- a/apps/desktop/src/locales/zh_TW/messages.json +++ b/apps/desktop/src/locales/zh_TW/messages.json @@ -1023,6 +1023,9 @@ "invalidVerificationCode": { "message": "無效的驗證碼" }, + "invalidEmailOrVerificationCode": { + "message": "Invalid email or verification code" + }, "continue": { "message": "繼續" }, @@ -4615,7 +4618,13 @@ "enterMultipleEmailsSeparatedByComma": { "message": "請以逗號分隔輸入多個電子郵件地址。" }, + "emailsRequiredChangeAccessType": { + "message": "Email verification requires at least one email address. To remove all emails, change the access type above." + }, "emailPlaceholder": { "message": "user@bitwarden.com , user@acme.com" + }, + "userVerificationFailed": { + "message": "User verification failed." } } diff --git a/apps/desktop/src/main/updater.main.ts b/apps/desktop/src/main/updater.main.ts index 60b4f282405..55f57c339fa 100644 --- a/apps/desktop/src/main/updater.main.ts +++ b/apps/desktop/src/main/updater.main.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { dialog, shell, Notification } from "electron"; import log from "electron-log"; import { autoUpdater, UpdateDownloadedEvent, VerifyUpdateSupport } from "electron-updater"; diff --git a/apps/desktop/src/main/window.main.ts b/apps/desktop/src/main/window.main.ts index 2872154aa44..b4ced4471fa 100644 --- a/apps/desktop/src/main/window.main.ts +++ b/apps/desktop/src/main/window.main.ts @@ -4,7 +4,7 @@ import { once } from "node:events"; import * as path from "path"; import * as url from "url"; -import { app, BrowserWindow, dialog, ipcMain, nativeTheme, screen, session } from "electron"; +import { app, BrowserWindow, ipcMain, nativeTheme, screen, session } from "electron"; import { concatMap, firstValueFrom, pairwise } from "rxjs"; import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; @@ -127,7 +127,6 @@ export class WindowMain { if (!isMacAppStore()) { const gotTheLock = app.requestSingleInstanceLock(); if (!gotTheLock) { - dialog.showErrorBox("Error", "An instance of Bitwarden Desktop is already running."); app.quit(); return; } else { diff --git a/apps/desktop/src/package-lock.json b/apps/desktop/src/package-lock.json index 0aa188eba2f..01c429ab3d0 100644 --- a/apps/desktop/src/package-lock.json +++ b/apps/desktop/src/package-lock.json @@ -1,12 +1,12 @@ { "name": "@bitwarden/desktop", - "version": "2026.2.0", + "version": "2026.2.1", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@bitwarden/desktop", - "version": "2026.2.0", + "version": "2026.2.1", "license": "GPL-3.0", "dependencies": { "@bitwarden/desktop-napi": "file:../desktop_native/napi" diff --git a/apps/desktop/src/package.json b/apps/desktop/src/package.json index 0076981ab60..fac797b5344 100644 --- a/apps/desktop/src/package.json +++ b/apps/desktop/src/package.json @@ -2,7 +2,7 @@ "name": "@bitwarden/desktop", "productName": "Bitwarden", "description": "A secure and free password manager for all of your devices.", - "version": "2026.2.0", + "version": "2026.2.1", "author": "Bitwarden Inc. (https://bitwarden.com)", "homepage": "https://bitwarden.com", "license": "GPL-3.0", diff --git a/apps/desktop/src/scss/base.scss b/apps/desktop/src/scss/base.scss index a95d82dacd4..18e782d341f 100644 --- a/apps/desktop/src/scss/base.scss +++ b/apps/desktop/src/scss/base.scss @@ -51,7 +51,7 @@ img { border: none; } -a { +a:not([bitlink]) { text-decoration: none; @include themify($themes) { @@ -102,23 +102,30 @@ textarea { div:not(.modal)::-webkit-scrollbar, .cdk-virtual-scroll-viewport::-webkit-scrollbar, -.vault-filters::-webkit-scrollbar { +.vault-filters::-webkit-scrollbar, +#bit-side-nav::-webkit-scrollbar { width: 10px; height: 10px; } div:not(.modal)::-webkit-scrollbar-track, .cdk-virtual-scroll-viewport::-webkit-scrollbar-track, -.vault-filters::-webkit-scrollbar-track { +.vault-filters::-webkit-scrollbar-track, +#bit-side-nav::-webkit-scrollbar-track { background-color: transparent; } div:not(.modal)::-webkit-scrollbar-thumb, .cdk-virtual-scroll-viewport::-webkit-scrollbar-thumb, -.vault-filters::-webkit-scrollbar-thumb { +.vault-filters::-webkit-scrollbar-thumb, +#bit-side-nav::-webkit-scrollbar-thumb { border-radius: 10px; margin-right: 1px; +} +div:not(.modal)::-webkit-scrollbar-thumb, +.cdk-virtual-scroll-viewport::-webkit-scrollbar-thumb, +.vault-filters::-webkit-scrollbar-thumb { @include themify($themes) { background-color: themed("scrollbarColor"); } @@ -130,6 +137,18 @@ div:not(.modal)::-webkit-scrollbar-thumb, } } +#bit-side-nav::-webkit-scrollbar-thumb { + @include themify($themes) { + background-color: themed("scrollbarColorNav"); + } + + &:hover { + @include themify($themes) { + background-color: themed("scrollbarHoverColorNav"); + } + } +} + // cdk-virtual-scroll .cdk-virtual-scroll-viewport { width: 100%; diff --git a/apps/desktop/src/scss/environment.scss b/apps/desktop/src/scss/environment.scss index 699f2246b4a..e6ea95ef17e 100644 --- a/apps/desktop/src/scss/environment.scss +++ b/apps/desktop/src/scss/environment.scss @@ -11,6 +11,8 @@ .vault > .groupings > .content > .inner-content { padding-top: 0; } + + --bit-sidenav-macos-extra-top-padding: 28px; } .environment-selector-btn { diff --git a/apps/desktop/src/scss/variables.scss b/apps/desktop/src/scss/variables.scss index a00257ed608..62d4f23ad46 100644 --- a/apps/desktop/src/scss/variables.scss +++ b/apps/desktop/src/scss/variables.scss @@ -56,13 +56,15 @@ $themes: ( backgroundColorAlt2: $background-color-alt2, scrollbarColor: rgba(100, 100, 100, 0.2), scrollbarHoverColor: rgba(100, 100, 100, 0.4), + scrollbarColorNav: rgba(226, 226, 226), + scrollbarHoverColorNav: rgba(197, 197, 197), boxBackgroundColor: $box-background-color, boxBackgroundHoverColor: $box-background-hover-color, boxBorderColor: $box-border-color, - headerBackgroundColor: rgb(var(--color-background-alt3)), - headerBorderColor: rgb(var(--color-background-alt4)), - headerInputBackgroundColor: darken($brand-primary, 8%), - headerInputBackgroundFocusColor: darken($brand-primary, 10%), + headerBackgroundColor: var(--color-sidenav-background), + headerBorderColor: var(--color-sidenav-active-item), + headerInputBackgroundColor: darken($brand-primary, 20%), + headerInputBackgroundFocusColor: darken($brand-primary, 25%), headerInputColor: #ffffff, headerInputPlaceholderColor: lighten($brand-primary, 35%), listItemBackgroundColor: $background-color, @@ -115,11 +117,13 @@ $themes: ( backgroundColorAlt2: #15181e, scrollbarColor: #6e788a, scrollbarHoverColor: #8d94a5, + scrollbarColorNav: #6e788a, + scrollbarHoverColorNav: #8d94a5, boxBackgroundColor: #2f343d, boxBackgroundHoverColor: #3c424e, boxBorderColor: #4c525f, - headerBackgroundColor: rgb(var(--color-background-alt3)), - headerBorderColor: rgb(var(--color-background-alt4)), + headerBackgroundColor: var(--color-sidenav-background), + headerBorderColor: var(--color-sidenav-active-item), headerInputBackgroundColor: #3c424e, headerInputBackgroundFocusColor: #4c525f, headerInputColor: #ffffff, diff --git a/apps/desktop/src/vault/app/vault/assign-collections/assign-collections-desktop.component.ts b/apps/desktop/src/vault/app/vault/assign-collections/assign-collections-desktop.component.ts index 5af1f96a569..43531b603d1 100644 --- a/apps/desktop/src/vault/app/vault/assign-collections/assign-collections-desktop.component.ts +++ b/apps/desktop/src/vault/app/vault/assign-collections/assign-collections-desktop.component.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { DIALOG_DATA, DialogConfig, DialogRef } from "@angular/cdk/dialog"; import { Component, Inject } from "@angular/core"; diff --git a/apps/desktop/src/vault/app/vault/vault-filter/filters/status-filter.component.ts b/apps/desktop/src/vault/app/vault/vault-filter/filters/status-filter.component.ts index 95ffd3f0212..5b9c32291dd 100644 --- a/apps/desktop/src/vault/app/vault/vault-filter/filters/status-filter.component.ts +++ b/apps/desktop/src/vault/app/vault/vault-filter/filters/status-filter.component.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { Component, viewChild } from "@angular/core"; import { combineLatest, firstValueFrom, map, switchMap } from "rxjs"; diff --git a/apps/desktop/src/vault/app/vault/vault-v2.component.ts b/apps/desktop/src/vault/app/vault/vault-v2.component.ts index 458ddd666b8..ae049325d8b 100644 --- a/apps/desktop/src/vault/app/vault/vault-v2.component.ts +++ b/apps/desktop/src/vault/app/vault/vault-v2.component.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { CommonModule } from "@angular/common"; import { ChangeDetectorRef, diff --git a/apps/web/package.json b/apps/web/package.json index ad778b03778..844ac1f12b5 100644 --- a/apps/web/package.json +++ b/apps/web/package.json @@ -1,6 +1,6 @@ { "name": "@bitwarden/web-vault", - "version": "2026.2.0", + "version": "2026.2.1", "scripts": { "build:oss": "webpack", "build:bit": "webpack -c ../../bitwarden_license/bit-web/webpack.config.js", diff --git a/apps/web/src/app/admin-console/common/base.events.component.ts b/apps/web/src/app/admin-console/common/base.events.component.ts index ba315dee7fb..dd1c393bc13 100644 --- a/apps/web/src/app/admin-console/common/base.events.component.ts +++ b/apps/web/src/app/admin-console/common/base.events.component.ts @@ -1,6 +1,6 @@ // FIXME: Update this file to be type safe and remove this and next line // @ts-strict-ignore -import { Directive, OnDestroy } from "@angular/core"; +import { Directive, OnDestroy, signal } from "@angular/core"; import { FormControl, FormGroup } from "@angular/forms"; import { ActivatedRoute } from "@angular/router"; import { combineLatest, filter, map, Observable, Subject, switchMap, takeUntil } from "rxjs"; @@ -22,9 +22,9 @@ import { EventExportService } from "../../tools/event-export"; @Directive() export abstract class BaseEventsComponent implements OnDestroy { - loading = true; - loaded = false; - events: EventView[]; + readonly loading = signal(true); + readonly loaded = signal(false); + readonly events = signal([]); dirtyDates = true; continuationToken: string; canUseSM = false; @@ -115,7 +115,7 @@ export abstract class BaseEventsComponent implements OnDestroy { return; } - this.loading = true; + this.loading.set(true); const dates = this.parseDates(); if (dates == null) { @@ -131,7 +131,7 @@ export abstract class BaseEventsComponent implements OnDestroy { } promise = null; - this.loading = false; + this.loading.set(false); }; loadEvents = async (clearExisting: boolean) => { @@ -140,7 +140,7 @@ export abstract class BaseEventsComponent implements OnDestroy { return; } - this.loading = true; + this.loading.set(true); let events: EventView[] = []; let promise: Promise; promise = this.loadAndParseEvents( @@ -153,14 +153,16 @@ export abstract class BaseEventsComponent implements OnDestroy { this.continuationToken = result.continuationToken; events = result.events; - if (!clearExisting && this.events != null && this.events.length > 0) { - this.events = this.events.concat(events); + if (!clearExisting && this.events() != null && this.events().length > 0) { + this.events.update((current) => { + return [...current, ...events]; + }); } else { - this.events = events; + this.events.set(events); } this.dirtyDates = false; - this.loading = false; + this.loading.set(false); promise = null; }; @@ -227,7 +229,7 @@ export abstract class BaseEventsComponent implements OnDestroy { private async export(start: string, end: string) { let continuationToken = this.continuationToken; - let events = [].concat(this.events); + let events = [].concat(this.events()); while (continuationToken != null) { const result = await this.loadAndParseEvents(start, end, continuationToken); 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 a641116f4de..1d9178e6fed 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 @@ -1,6 +1,6 @@ import { ChangeDetectorRef, Component, NgZone, OnDestroy, OnInit, ViewChild } from "@angular/core"; import { takeUntilDestroyed } from "@angular/core/rxjs-interop"; -import { ActivatedRoute, Params, Router } from "@angular/router"; +import { ActivatedRoute, NavigationExtras, Params, Router } from "@angular/router"; import { BehaviorSubject, combineLatest, @@ -588,7 +588,7 @@ export class VaultComponent implements OnInit, OnDestroy { queryParamsHandling: "merge", replaceUrl: true, state: { - focusMainAfterNav: false, + focusAfterNav: false, }, }), ); @@ -812,7 +812,7 @@ export class VaultComponent implements OnInit, OnDestroy { async editCipherAttachments(cipher: CipherView) { if (cipher.reprompt !== 0 && !(await this.passwordRepromptService.showPasswordPrompt())) { - this.go({ cipherId: null, itemId: null }); + this.go({ cipherId: null, itemId: null }, this.configureRouterFocusToCipher(cipher.id)); return; } @@ -869,7 +869,7 @@ export class VaultComponent implements OnInit, OnDestroy { !(await this.passwordRepromptService.showPasswordPrompt()) ) { // didn't pass password prompt, so don't open add / edit modal - this.go({ cipherId: null, itemId: null }); + this.go({ cipherId: null, itemId: null }, this.configureRouterFocusToCipher(cipher.id)); return; } @@ -893,7 +893,10 @@ export class VaultComponent implements OnInit, OnDestroy { !(await this.passwordRepromptService.showPasswordPrompt()) ) { // Didn't pass password prompt, so don't open add / edit modal. - await this.go({ cipherId: null, itemId: null, action: null }); + await this.go( + { cipherId: null, itemId: null, action: null }, + this.configureRouterFocusToCipher(cipher.id), + ); return; } @@ -943,7 +946,10 @@ export class VaultComponent implements OnInit, OnDestroy { } // Clear the query params when the dialog closes - await this.go({ cipherId: null, itemId: null, action: null }); + await this.go( + { cipherId: null, itemId: null, action: null }, + this.configureRouterFocusToCipher(formConfig.originalCipher?.id), + ); } async cloneCipher(cipher: CipherView) { @@ -1422,7 +1428,25 @@ export class VaultComponent implements OnInit, OnDestroy { } } - private go(queryParams: any = null) { + /** + * Helper function to set up the `state.focusAfterNav` property for dialog router navigation if + * the cipherId exists. If it doesn't exist, returns undefined. + * + * This ensures that when the routed dialog is closed, the focus returns to the cipher button in + * the vault table, which allows keyboard users to continue navigating uninterrupted. + * + * @param cipherId id of cipher + * @returns Partial, specifically the state.focusAfterNav property, or undefined + */ + private configureRouterFocusToCipher(cipherId?: string): Partial | undefined { + if (cipherId) { + return { + state: { focusAfterNav: `#cipher-btn-${cipherId}` }, + }; + } + } + + private go(queryParams: any = null, navigateOptions?: NavigationExtras) { if (queryParams == null) { queryParams = { type: this.activeFilter.cipherType, @@ -1436,6 +1460,7 @@ export class VaultComponent implements OnInit, OnDestroy { queryParams: queryParams, queryParamsHandling: "merge", replaceUrl: true, + ...navigateOptions, }); } diff --git a/apps/web/src/app/admin-console/organizations/manage/events.component.html b/apps/web/src/app/admin-console/organizations/manage/events.component.html index 83665a4b99e..3e76c8c879b 100644 --- a/apps/web/src/app/admin-console/organizations/manage/events.component.html +++ b/apps/web/src/app/admin-console/organizations/manage/events.component.html @@ -1,14 +1,16 @@ @let usePlaceHolderEvents = !organization?.useEvents; + - - {{ "upgrade" | i18n }} - + @if (usePlaceHolderEvents) { + + {{ "upgrade" | i18n }} + + }
@@ -61,79 +63,87 @@
- - {{ "upgradeEventLogMessage" | i18n }} - - - - {{ "loading" | i18n }} - - - @let displayedEvents = organization?.useEvents ? events : placeholderEvents; +@if (loaded() && usePlaceHolderEvents) { + + {{ "upgradeEventLogMessage" | i18n }} + +} -

{{ "noEventsInList" | i18n }}

- - - - {{ "timestamp" | i18n }} - {{ "client" | i18n }} - {{ "member" | i18n }} - {{ "event" | i18n }} - - - - - - {{ i > 4 && usePlaceHolderEvents ? "******" : (e.date | date: "medium") }} - - - {{ e.appName }} - - - {{ e.userName }} - - - - - - -
+@if (!loaded()) { + + + {{ "loading" | i18n }} + +} +@if (loaded()) { + + @let displayedEvents = organization?.useEvents ? events() : placeholderEvents; - -
-
- + @if (!displayedEvents || !displayedEvents.length) { +

{{ "noEventsInList" | i18n }}

+ } -

- {{ "upgradeEventLogTitleMessage" | i18n }} -

-

- {{ "upgradeForFullEventsMessage" | i18n }} -

- - + } + +} + +@if (loaded() && usePlaceHolderEvents) { + +
+
+ + +

+ {{ "upgradeEventLogTitleMessage" | i18n }} +

+

+ {{ "upgradeForFullEventsMessage" | i18n }} +

+ + +
-
- + +} diff --git a/apps/web/src/app/admin-console/organizations/manage/events.component.ts b/apps/web/src/app/admin-console/organizations/manage/events.component.ts index 62f6539cc16..01d6515c486 100644 --- a/apps/web/src/app/admin-console/organizations/manage/events.component.ts +++ b/apps/web/src/app/admin-console/organizations/manage/events.component.ts @@ -1,6 +1,6 @@ // FIXME: Update this file to be type safe and remove this and next line // @ts-strict-ignore -import { Component, OnDestroy, OnInit } from "@angular/core"; +import { Component, OnDestroy, OnInit, ChangeDetectionStrategy } from "@angular/core"; import { ActivatedRoute } from "@angular/router"; import { concatMap, firstValueFrom, lastValueFrom, map, of, switchMap, takeUntil, tap } from "rxjs"; @@ -44,11 +44,11 @@ const EVENT_SYSTEM_USER_TO_TRANSLATION: Record = { [EventSystemUser.SCIM]: null, // SCIM acronym not able to be translated so just display SCIM [EventSystemUser.DomainVerification]: "domainVerification", [EventSystemUser.PublicApi]: "publicApi", + [EventSystemUser.BitwardenPortal]: "system", }; -// FIXME(https://bitwarden.atlassian.net/browse/CL-764): Migrate to OnPush -// eslint-disable-next-line @angular-eslint/prefer-on-push-component-change-detection @Component({ + changeDetection: ChangeDetectionStrategy.OnPush, templateUrl: "events.component.html", imports: [SharedModule, HeaderModule], }) @@ -167,7 +167,7 @@ export class EventsComponent extends BaseEventsComponent implements OnInit, OnDe } } await this.refreshEvents(); - this.loaded = true; + this.loaded.set(true); } protected requestEvents(startDate: string, endDate: string, continuationToken: string) { diff --git a/apps/web/src/app/admin-console/organizations/reporting/organization-reporting.module.ts b/apps/web/src/app/admin-console/organizations/reporting/organization-reporting.module.ts index 46599d7da46..d96e2cbb6c0 100644 --- a/apps/web/src/app/admin-console/organizations/reporting/organization-reporting.module.ts +++ b/apps/web/src/app/admin-console/organizations/reporting/organization-reporting.module.ts @@ -1,3 +1,4 @@ +import { OverlayModule } from "@angular/cdk/overlay"; import { NgModule } from "@angular/core"; import { ReportsSharedModule } from "../../../dirt/reports"; @@ -8,7 +9,13 @@ import { OrganizationReportingRoutingModule } from "./organization-reporting-rou import { ReportsHomeComponent } from "./reports-home.component"; @NgModule({ - imports: [SharedModule, ReportsSharedModule, OrganizationReportingRoutingModule, HeaderModule], + imports: [ + SharedModule, + OverlayModule, + ReportsSharedModule, + OrganizationReportingRoutingModule, + HeaderModule, + ], declarations: [ReportsHomeComponent], }) export class OrganizationReportingModule {} diff --git a/apps/web/src/app/admin-console/organizations/reporting/reports-home.component.html b/apps/web/src/app/admin-console/organizations/reporting/reports-home.component.html index 59eac5b6300..9a931f66af9 100644 --- a/apps/web/src/app/admin-console/organizations/reporting/reports-home.component.html +++ b/apps/web/src/app/admin-console/organizations/reporting/reports-home.component.html @@ -8,9 +8,26 @@ - +@if (!(homepage$ | async)) { + + +} + + + + diff --git a/apps/web/src/app/admin-console/organizations/reporting/reports-home.component.ts b/apps/web/src/app/admin-console/organizations/reporting/reports-home.component.ts index 6043bfd3193..503a4f88050 100644 --- a/apps/web/src/app/admin-console/organizations/reporting/reports-home.component.ts +++ b/apps/web/src/app/admin-console/organizations/reporting/reports-home.component.ts @@ -1,6 +1,18 @@ // FIXME: Update this file to be type safe and remove this and next line // @ts-strict-ignore -import { Component, OnInit } from "@angular/core"; +import { Overlay, OverlayRef } from "@angular/cdk/overlay"; +import { TemplatePortal } from "@angular/cdk/portal"; +import { + AfterViewInit, + Component, + inject, + OnDestroy, + OnInit, + TemplateRef, + viewChild, + ViewContainerRef, +} from "@angular/core"; +import { takeUntilDestroyed } from "@angular/core/rxjs-interop"; import { ActivatedRoute, NavigationEnd, Router } from "@angular/router"; import { filter, map, Observable, startWith, concatMap, firstValueFrom } from "rxjs"; @@ -21,16 +33,30 @@ import { ReportVariant, reports, ReportType, ReportEntry } from "../../../dirt/r templateUrl: "reports-home.component.html", standalone: false, }) -export class ReportsHomeComponent implements OnInit { +export class ReportsHomeComponent implements OnInit, AfterViewInit, OnDestroy { reports$: Observable; homepage$: Observable; + private readonly backButtonTemplate = + viewChild.required>("backButtonTemplate"); + + private overlayRef: OverlayRef | null = null; + private overlay = inject(Overlay); + private viewContainerRef = inject(ViewContainerRef); + constructor( private route: ActivatedRoute, private organizationService: OrganizationService, private accountService: AccountService, private router: Router, - ) {} + ) { + this.router.events + .pipe( + takeUntilDestroyed(), + filter((event) => event instanceof NavigationEnd), + ) + .subscribe(() => this.updateOverlay()); + } async ngOnInit() { this.homepage$ = this.router.events.pipe( @@ -51,6 +77,46 @@ export class ReportsHomeComponent implements OnInit { ); } + ngAfterViewInit(): void { + this.updateOverlay(); + } + + ngOnDestroy(): void { + this.overlayRef?.dispose(); + } + + returnFocusToPage(event: Event): void { + if ((event as KeyboardEvent).shiftKey) { + return; // Allow natural Shift+Tab behavior + } + event.preventDefault(); + const firstFocusable = document.querySelector( + "[cdktrapfocus] a:not([tabindex='-1'])", + ) as HTMLElement; + firstFocusable?.focus(); + } + + focusOverlayButton(event: Event): void { + if ((event as KeyboardEvent).shiftKey) { + return; // Allow natural Shift+Tab behavior + } + event.preventDefault(); + const button = this.overlayRef?.overlayElement?.querySelector("a") as HTMLElement; + button?.focus(); + } + + private updateOverlay(): void { + if (this.isReportsHomepageRouteUrl(this.router.url)) { + this.overlayRef?.dispose(); + this.overlayRef = null; + } else if (!this.overlayRef) { + this.overlayRef = this.overlay.create({ + positionStrategy: this.overlay.position().global().bottom("20px").right("32px"), + }); + this.overlayRef.attach(new TemplatePortal(this.backButtonTemplate(), this.viewContainerRef)); + } + } + private buildReports(productType: ProductTierType): ReportEntry[] { const reportRequiresUpgrade = productType == ProductTierType.Free ? ReportVariant.RequiresUpgrade : ReportVariant.Enabled; diff --git a/apps/web/src/app/admin-console/organizations/settings/organization-settings.module.ts b/apps/web/src/app/admin-console/organizations/settings/organization-settings.module.ts index 27a6226f964..13467e222d2 100644 --- a/apps/web/src/app/admin-console/organizations/settings/organization-settings.module.ts +++ b/apps/web/src/app/admin-console/organizations/settings/organization-settings.module.ts @@ -4,9 +4,9 @@ import { PremiumBadgeComponent } from "@bitwarden/angular/billing/components/pre import { ItemModule } from "@bitwarden/components"; import { DangerZoneComponent } from "../../../auth/settings/account/danger-zone.component"; +import { AccountFingerprintComponent } from "../../../key-management/account-fingerprint/account-fingerprint.component"; import { HeaderModule } from "../../../layouts/header/header.module"; import { SharedModule } from "../../../shared"; -import { AccountFingerprintComponent } from "../../../shared/components/account-fingerprint/account-fingerprint.component"; import { AccountComponent } from "./account.component"; import { OrganizationSettingsRoutingModule } from "./organization-settings-routing.module"; diff --git a/apps/web/src/app/admin-console/organizations/shared/components/access-selector/access-selector-dialog.stories.ts b/apps/web/src/app/admin-console/organizations/shared/components/access-selector/access-selector-dialog.stories.ts index 3e23eff13a9..f5c6939c284 100644 --- a/apps/web/src/app/admin-console/organizations/shared/components/access-selector/access-selector-dialog.stories.ts +++ b/apps/web/src/app/admin-console/organizations/shared/components/access-selector/access-selector-dialog.stories.ts @@ -25,7 +25,7 @@ const render: Story["render"] = (args) => ({ ...args, }, template: ` - + Access selector

- - {{ "loading" | i18n }} +

diff --git a/apps/web/src/app/auth/organization-invite/accept-organization.component.html b/apps/web/src/app/auth/organization-invite/accept-organization.component.html index cc08b840c30..63bfbf3ef0b 100644 --- a/apps/web/src/app/auth/organization-invite/accept-organization.component.html +++ b/apps/web/src/app/auth/organization-invite/accept-organization.component.html @@ -2,12 +2,11 @@

- - {{ "loading" | i18n }} +

diff --git a/apps/web/src/app/auth/settings/account/change-avatar-dialog.component.html b/apps/web/src/app/auth/settings/account/change-avatar-dialog.component.html index 7e8b69a0c48..e9365fa41a4 100644 --- a/apps/web/src/app/auth/settings/account/change-avatar-dialog.component.html +++ b/apps/web/src/app/auth/settings/account/change-avatar-dialog.component.html @@ -1,7 +1,7 @@
- + {{ "loading" | i18n }}

{{ "pickAnAvatarColor" | i18n }}

@@ -31,10 +31,11 @@ class="tw-relative tw-flex tw-size-24 tw-cursor-pointer tw-place-content-center tw-rounded-full tw-border tw-border-solid tw-border-secondary-600 tw-outline tw-outline-0 tw-outline-offset-1 hover:tw-outline-1 hover:tw-outline-primary-300 focus:tw-outline-2 focus:tw-outline-primary-600" [style.background-color]="customColor$ | async" > - + class="!tw-text-muted tw-m-auto tw-text-3xl" + > - - {{ "loading" | i18n }} +
@@ -32,8 +31,8 @@ appStopProp [bitAction]="openChangeAvatar" > - - Customize + + {{ "customize" | i18n }}
@@ -43,7 +42,7 @@ rel="noopener noreferrer" href="https://bitwarden.com/help/claimed-accounts" > - +
@if (fingerprintMaterial && userPublicKey) { diff --git a/apps/web/src/app/auth/settings/account/profile.component.ts b/apps/web/src/app/auth/settings/account/profile.component.ts index fd96f343b3a..24e8a370e2a 100644 --- a/apps/web/src/app/auth/settings/account/profile.component.ts +++ b/apps/web/src/app/auth/settings/account/profile.component.ts @@ -18,8 +18,8 @@ import { DialogService, ToastService } from "@bitwarden/components"; import { KeyService } from "@bitwarden/key-management"; import { DynamicAvatarComponent } from "../../../components/dynamic-avatar.component"; +import { AccountFingerprintComponent } from "../../../key-management/account-fingerprint/account-fingerprint.component"; import { SharedModule } from "../../../shared"; -import { AccountFingerprintComponent } from "../../../shared/components/account-fingerprint/account-fingerprint.component"; import { ChangeAvatarDialogComponent } from "./change-avatar-dialog.component"; diff --git a/apps/web/src/app/auth/settings/emergency-access/emergency-access-add-edit.component.html b/apps/web/src/app/auth/settings/emergency-access/emergency-access-add-edit.component.html index 1c04c03a8d2..0ba4b29690b 100644 --- a/apps/web/src/app/auth/settings/emergency-access/emergency-access-add-edit.component.html +++ b/apps/web/src/app/auth/settings/emergency-access/emergency-access-add-edit.component.html @@ -25,7 +25,7 @@ href="https://bitwarden.com/help/emergency-access/#user-access" slot="end" > - + diff --git a/apps/web/src/app/auth/settings/emergency-access/emergency-access.component.html b/apps/web/src/app/auth/settings/emergency-access/emergency-access.component.html index 70165a94fc3..fe34a63d349 100644 --- a/apps/web/src/app/auth/settings/emergency-access/emergency-access.component.html +++ b/apps/web/src/app/auth/settings/emergency-access/emergency-access.component.html @@ -30,7 +30,7 @@ [bitAction]="invite" [disabled]="!(canAccessPremium$ | async)" > - + {{ "addEmergencyContact" | i18n }} @@ -100,7 +100,7 @@ *ngIf="c.status === emergencyAccessStatusType.Invited" (click)="reinvite(c)" > - + {{ "resendInvitation" | i18n }} @@ -145,12 +145,11 @@

{{ "noTrustedContacts" | i18n }}

- - {{ "loading" | i18n }} +
@@ -223,7 +222,7 @@ *ngIf="c.status === emergencyAccessStatusType.Confirmed" (click)="requestAccess(c)" > - + {{ "requestAccess" | i18n }} @@ -262,12 +261,11 @@

{{ "noGrantedAccess" | i18n }}

- - {{ "loading" | i18n }} +
diff --git a/apps/web/src/app/auth/settings/emergency-access/takeover/emergency-access-takeover-dialog.component.html b/apps/web/src/app/auth/settings/emergency-access/takeover/emergency-access-takeover-dialog.component.html index 2e0a81da976..e5b81671d7f 100644 --- a/apps/web/src/app/auth/settings/emergency-access/takeover/emergency-access-takeover-dialog.component.html +++ b/apps/web/src/app/auth/settings/emergency-access/takeover/emergency-access-takeover-dialog.component.html @@ -7,12 +7,11 @@
@if (initializing) {
- - {{ "loading" | i18n }} +
} @else { diff --git a/apps/web/src/app/auth/settings/emergency-access/takeover/emergency-access-takeover-dialog.component.ts b/apps/web/src/app/auth/settings/emergency-access/takeover/emergency-access-takeover-dialog.component.ts index 743f41537e9..09b6934d2d2 100644 --- a/apps/web/src/app/auth/settings/emergency-access/takeover/emergency-access-takeover-dialog.component.ts +++ b/apps/web/src/app/auth/settings/emergency-access/takeover/emergency-access-takeover-dialog.component.ts @@ -21,6 +21,7 @@ import { DialogModule, DialogRef, DialogService, + IconModule, ToastService, } from "@bitwarden/components"; import { I18nPipe } from "@bitwarden/ui-common"; @@ -59,6 +60,7 @@ export type EmergencyAccessTakeoverDialogResultType = CommonModule, DialogModule, I18nPipe, + IconModule, InputPasswordComponent, ], }) diff --git a/apps/web/src/app/auth/settings/emergency-access/view/emergency-access-view.component.html b/apps/web/src/app/auth/settings/emergency-access/view/emergency-access-view.component.html index 4aaac6aaa52..6c9637ab921 100644 --- a/apps/web/src/app/auth/settings/emergency-access/view/emergency-access-view.component.html +++ b/apps/web/src/app/auth/settings/emergency-access/view/emergency-access-view.component.html @@ -18,22 +18,20 @@ >{{ currentCipher.name }} - - {{ "shared" | i18n }} + [ariaLabel]="'shared' | i18n" + > - - {{ "attachments" | i18n }} + [ariaLabel]="'attachments' | i18n" + >
{{ currentCipher.subTitle }} @@ -43,11 +41,10 @@ - - {{ "loading" | i18n }} +
diff --git a/apps/web/src/app/auth/settings/two-factor/two-factor-setup-authenticator.component.html b/apps/web/src/app/auth/settings/two-factor/two-factor-setup-authenticator.component.html index 1595c0350d0..a2f39ea3c21 100644 --- a/apps/web/src/app/auth/settings/two-factor/two-factor-setup-authenticator.component.html +++ b/apps/web/src/app/auth/settings/two-factor/two-factor-setup-authenticator.component.html @@ -73,7 +73,7 @@

- +

{{ "twoStepAuthenticatorQRCanvasError" | i18n }}

diff --git a/apps/web/src/app/auth/settings/two-factor/two-factor-setup-authenticator.component.ts b/apps/web/src/app/auth/settings/two-factor/two-factor-setup-authenticator.component.ts index d27e8ffecce..0c512b7b99a 100644 --- a/apps/web/src/app/auth/settings/two-factor/two-factor-setup-authenticator.component.ts +++ b/apps/web/src/app/auth/settings/two-factor/two-factor-setup-authenticator.component.ts @@ -29,6 +29,7 @@ import { DialogRef, DialogService, FormFieldModule, + IconModule, SvgModule, InputModule, LinkModule, @@ -63,6 +64,7 @@ declare global { ReactiveFormsModule, DialogModule, FormFieldModule, + IconModule, InputModule, LinkModule, TypographyModule, diff --git a/apps/web/src/app/auth/settings/two-factor/two-factor-setup-webauthn.component.html b/apps/web/src/app/auth/settings/two-factor/two-factor-setup-webauthn.component.html index 8a538cb961c..31ac01fe1da 100644 --- a/apps/web/src/app/auth/settings/two-factor/two-factor-setup-webauthn.component.html +++ b/apps/web/src/app/auth/settings/two-factor/two-factor-setup-webauthn.component.html @@ -17,7 +17,7 @@
  • - + {{ k.name || ("unnamedKey" | i18n) }} @@ -27,12 +27,12 @@ - + > - {{ "remove" | i18n }} @@ -68,19 +68,27 @@ {{ "readKey" | i18n }} - + - + {{ "twoFactorU2fWaiting" | i18n }}... - + {{ "twoFactorU2fClickSave" | i18n }} - + {{ "twoFactorU2fProblemReadingTryAgain" | i18n }} diff --git a/apps/web/src/app/auth/settings/two-factor/two-factor-setup-webauthn.component.ts b/apps/web/src/app/auth/settings/two-factor/two-factor-setup-webauthn.component.ts index 57001acc4d2..22cc6e9d9b3 100644 --- a/apps/web/src/app/auth/settings/two-factor/two-factor-setup-webauthn.component.ts +++ b/apps/web/src/app/auth/settings/two-factor/two-factor-setup-webauthn.component.ts @@ -27,6 +27,7 @@ import { DialogRef, DialogService, FormFieldModule, + IconModule, LinkModule, ToastService, TypographyModule, @@ -56,6 +57,7 @@ interface Key { DialogModule, FormFieldModule, I18nPipe, + IconModule, JslibModule, LinkModule, ReactiveFormsModule, diff --git a/apps/web/src/app/auth/settings/two-factor/two-factor-setup.component.html b/apps/web/src/app/auth/settings/two-factor/two-factor-setup.component.html index 69a0dbf4145..77c410e8ec6 100644 --- a/apps/web/src/app/auth/settings/two-factor/two-factor-setup.component.html +++ b/apps/web/src/app/auth/settings/two-factor/two-factor-setup.component.html @@ -34,12 +34,11 @@

    {{ "providers" | i18n }} - - {{ "loading" | i18n }} +

    @@ -59,12 +58,11 @@ {{ p.name }} - - {{ "enabled" | i18n }} + diff --git a/apps/web/src/app/auth/settings/webauthn-login-settings/delete-credential-dialog/delete-credential-dialog.component.html b/apps/web/src/app/auth/settings/webauthn-login-settings/delete-credential-dialog/delete-credential-dialog.component.html index 147fc9874dd..910da970ff0 100644 --- a/apps/web/src/app/auth/settings/webauthn-login-settings/delete-credential-dialog/delete-credential-dialog.component.html +++ b/apps/web/src/app/auth/settings/webauthn-login-settings/delete-credential-dialog/delete-credential-dialog.component.html @@ -8,7 +8,11 @@ - + diff --git a/apps/web/src/app/auth/settings/webauthn-login-settings/enable-encryption-dialog/enable-encryption-dialog.component.html b/apps/web/src/app/auth/settings/webauthn-login-settings/enable-encryption-dialog/enable-encryption-dialog.component.html index 3fe6f43a052..85fa35ed4da 100644 --- a/apps/web/src/app/auth/settings/webauthn-login-settings/enable-encryption-dialog/enable-encryption-dialog.component.html +++ b/apps/web/src/app/auth/settings/webauthn-login-settings/enable-encryption-dialog/enable-encryption-dialog.component.html @@ -8,7 +8,11 @@ - + diff --git a/apps/web/src/app/auth/settings/webauthn-login-settings/webauthn-login-settings.component.html b/apps/web/src/app/auth/settings/webauthn-login-settings/webauthn-login-settings.component.html index 2ef177922a9..dd260848f52 100644 --- a/apps/web/src/app/auth/settings/webauthn-login-settings/webauthn-login-settings.component.html +++ b/apps/web/src/app/auth/settings/webauthn-login-settings/webauthn-login-settings.component.html @@ -21,7 +21,7 @@ - +

    @@ -36,7 +36,7 @@ {{ credential.name }} - + {{ "usedForEncryption" | i18n }} @@ -47,7 +47,7 @@ [attr.aria-label]="('enablePasskeyEncryption' | i18n) + ' ' + credential.name" (click)="enableEncryption(credential.id)" > - + {{ "enablePasskeyEncryption" | i18n }} diff --git a/apps/web/src/app/auth/shared/components/user-verification/user-verification.component.html b/apps/web/src/app/auth/shared/components/user-verification/user-verification.component.html index 0770ea4dfe1..1f16fe817e1 100644 --- a/apps/web/src/app/auth/shared/components/user-verification/user-verification.component.html +++ b/apps/web/src/app/auth/shared/components/user-verification/user-verification.component.html @@ -21,7 +21,7 @@ {{ "sendCode" | i18n }} - + {{ "codeSent" | i18n }} diff --git a/apps/web/src/app/auth/verify-email-token.component.html b/apps/web/src/app/auth/verify-email-token.component.html index 63437352e19..47e0d0f1517 100644 --- a/apps/web/src/app/auth/verify-email-token.component.html +++ b/apps/web/src/app/auth/verify-email-token.component.html @@ -1,6 +1,10 @@

    Bitwarden
    - +
    diff --git a/apps/web/src/app/auth/verify-email-token.component.ts b/apps/web/src/app/auth/verify-email-token.component.ts index fe70f876bc4..f05f9c08b76 100644 --- a/apps/web/src/app/auth/verify-email-token.component.ts +++ b/apps/web/src/app/auth/verify-email-token.component.ts @@ -12,11 +12,14 @@ import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.servic import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; import { ToastService } from "@bitwarden/components"; +import { SharedModule } from "../shared/shared.module"; + // FIXME(https://bitwarden.atlassian.net/browse/CL-764): Migrate to OnPush // eslint-disable-next-line @angular-eslint/prefer-on-push-component-change-detection @Component({ selector: "app-verify-email-token", templateUrl: "verify-email-token.component.html", + imports: [SharedModule], }) export class VerifyEmailTokenComponent implements OnInit { constructor( diff --git a/apps/web/src/app/billing/organizations/organization-plans.component.spec.ts b/apps/web/src/app/billing/organizations/organization-plans.component.spec.ts new file mode 100644 index 00000000000..aa4cbdab40e --- /dev/null +++ b/apps/web/src/app/billing/organizations/organization-plans.component.spec.ts @@ -0,0 +1,2199 @@ +// These are disabled until we can migrate to signals and remove the use of @Input properties that are used within the mocked child components +/* eslint-disable @angular-eslint/prefer-output-emitter-ref */ +/* eslint-disable @angular-eslint/prefer-signals */ +import { ChangeDetectionStrategy, Component, EventEmitter, Input, Output } from "@angular/core"; +import { ComponentFixture, fakeAsync, TestBed, tick } from "@angular/core/testing"; +import { FormBuilder, Validators } from "@angular/forms"; +import { Router } from "@angular/router"; +import { BehaviorSubject, of } from "rxjs"; + +import { ApiService } from "@bitwarden/common/abstractions/api.service"; +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 { PolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction"; +import { ProviderApiServiceAbstraction } from "@bitwarden/common/admin-console/abstractions/provider/provider-api.service.abstraction"; +import { Organization } from "@bitwarden/common/admin-console/models/domain/organization"; +import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; +import { PlanType, ProductTierType } from "@bitwarden/common/billing/enums"; +import { PlanResponse } from "@bitwarden/common/billing/models/response/plan.response"; +import { EncryptService } from "@bitwarden/common/key-management/crypto/abstractions/encrypt.service"; +import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; +import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; +import { MessagingService } from "@bitwarden/common/platform/abstractions/messaging.service"; +import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; +import { SyncService } from "@bitwarden/common/vault/abstractions/sync/sync.service.abstraction"; +import { ToastService } from "@bitwarden/components"; +import { KeyService } from "@bitwarden/key-management"; +import { + PreviewInvoiceClient, + SubscriberBillingClient, +} from "@bitwarden/web-vault/app/billing/clients"; + +import { OrganizationInformationComponent } from "../../admin-console/organizations/create/organization-information.component"; +import { EnterBillingAddressComponent, EnterPaymentMethodComponent } from "../payment/components"; +import { SecretsManagerSubscribeComponent } from "../shared"; +import { OrganizationSelfHostingLicenseUploaderComponent } from "../shared/self-hosting-license-uploader/organization-self-hosting-license-uploader.component"; + +import { OrganizationPlansComponent } from "./organization-plans.component"; + +// Mocked Child Components +@Component({ + changeDetection: ChangeDetectionStrategy.OnPush, + selector: "app-org-info", + template: "", + standalone: true, +}) +class MockOrgInfoComponent { + @Input() formGroup: any; + @Input() createOrganization = true; + @Input() isProvider = false; + @Input() acceptingSponsorship = false; + @Output() changedBusinessOwned = new EventEmitter(); +} + +@Component({ + changeDetection: ChangeDetectionStrategy.OnPush, + selector: "sm-subscribe", + template: "", + standalone: true, +}) +class MockSmSubscribeComponent { + @Input() formGroup: any; + @Input() selectedPlan: any; + @Input() upgradeOrganization = false; +} + +@Component({ + changeDetection: ChangeDetectionStrategy.OnPush, + selector: "app-enter-payment-method", + template: "", + standalone: true, +}) +class MockEnterPaymentMethodComponent { + @Input() group: any; + + static getFormGroup() { + const fb = new FormBuilder(); + return fb.group({ + type: fb.control("card"), + bankAccount: fb.group({ + routingNumber: fb.control(""), + accountNumber: fb.control(""), + accountHolderName: fb.control(""), + accountHolderType: fb.control(""), + }), + billingAddress: fb.group({ + country: fb.control("US"), + postalCode: fb.control(""), + }), + }); + } + + tokenize = jest.fn().mockResolvedValue({ + token: "mock_token", + type: "card", + }); +} + +@Component({ + changeDetection: ChangeDetectionStrategy.OnPush, + selector: "app-enter-billing-address", + template: "", + standalone: true, +}) +class MockEnterBillingAddressComponent { + @Input() group: any; + @Input() scenario: any; + + static getFormGroup() { + return new FormBuilder().group({ + country: ["US", Validators.required], + postalCode: ["", Validators.required], + taxId: [""], + line1: [""], + line2: [""], + city: [""], + state: [""], + }); + } +} + +@Component({ + changeDetection: ChangeDetectionStrategy.OnPush, + selector: "organization-self-hosting-license-uploader", + template: "", + standalone: true, +}) +class MockOrganizationSelfHostingLicenseUploaderComponent { + @Output() onLicenseFileUploaded = new EventEmitter(); +} + +// Test Helper Functions + +/** + * Sets up mock encryption keys and org key services + */ +const setupMockEncryptionKeys = ( + mockKeyService: jest.Mocked, + mockEncryptService: jest.Mocked, +) => { + mockKeyService.makeOrgKey.mockResolvedValue([{ encryptedString: "mock-key" }, {} as any] as any); + + mockEncryptService.encryptString.mockResolvedValue({ + encryptedString: "mock-collection", + } as any); + + mockKeyService.makeKeyPair.mockResolvedValue([ + "public-key", + { encryptedString: "private-key" }, + ] as any); +}; + +/** + * Sets up a mock payment method component that returns a successful tokenization + */ +const setupMockPaymentMethodComponent = ( + component: OrganizationPlansComponent, + token = "mock_token", + type = "card", +) => { + component["enterPaymentMethodComponent"] = { + tokenize: jest.fn().mockResolvedValue({ token, type }), + } as any; +}; + +/** + * Patches billing address form with standard test values + */ +const patchBillingAddress = ( + component: OrganizationPlansComponent, + overrides: Partial<{ + country: string; + postalCode: string; + line1: string; + line2: string; + city: string; + state: string; + taxId: string; + }> = {}, +) => { + component.billingFormGroup.controls.billingAddress.patchValue({ + country: "US", + postalCode: "12345", + line1: "123 Street", + line2: "", + city: "City", + state: "CA", + taxId: "", + ...overrides, + }); +}; + +/** + * Sets up a mock organization for upgrade scenarios + */ +const setupMockUpgradeOrganization = ( + mockOrganizationApiService: jest.Mocked, + organizationsSubject: BehaviorSubject, + orgConfig: { + id?: string; + productTierType?: ProductTierType; + hasPaymentSource?: boolean; + planType?: PlanType; + seats?: number; + maxStorageGb?: number; + hasPublicAndPrivateKeys?: boolean; + useSecretsManager?: boolean; + smSeats?: number; + smServiceAccounts?: number; + } = {}, +) => { + const { + id = "org-123", + productTierType = ProductTierType.Free, + hasPaymentSource = true, + planType = PlanType.Free, + seats = 5, + maxStorageGb, + hasPublicAndPrivateKeys = true, + useSecretsManager = false, + smSeats, + smServiceAccounts, + } = orgConfig; + + const mockOrganization = { + id, + name: "Test Org", + productTierType, + seats, + maxStorageGb, + hasPublicAndPrivateKeys, + useSecretsManager, + } as Organization; + + organizationsSubject.next([mockOrganization]); + + mockOrganizationApiService.getBilling.mockResolvedValue({ + paymentSource: hasPaymentSource ? { type: "card" } : null, + } as any); + + mockOrganizationApiService.getSubscription.mockResolvedValue({ + planType, + smSeats, + smServiceAccounts, + } as any); + + return mockOrganization; +}; + +/** + * Patches organization form with basic test values + */ +const patchOrganizationForm = ( + component: OrganizationPlansComponent, + values: { + name?: string; + billingEmail?: string; + productTier?: ProductTierType; + plan?: PlanType; + additionalSeats?: number; + additionalStorage?: number; + }, +) => { + component.formGroup.patchValue({ + name: "Test Org", + billingEmail: "test@example.com", + productTier: ProductTierType.Free, + plan: PlanType.Free, + additionalSeats: 0, + additionalStorage: 0, + ...values, + }); +}; + +/** + * Returns plan details + * + */ + +const createMockPlans = (): PlanResponse[] => { + return [ + { + type: PlanType.Free, + productTier: ProductTierType.Free, + name: "Free", + isAnnual: true, + upgradeSortOrder: 1, + displaySortOrder: 1, + PasswordManager: { + basePrice: 0, + seatPrice: 0, + maxSeats: 2, + baseSeats: 2, + hasAdditionalSeatsOption: false, + hasAdditionalStorageOption: false, + hasPremiumAccessOption: false, + baseStorageGb: 0, + }, + SecretsManager: null, + } as PlanResponse, + { + type: PlanType.FamiliesAnnually, + productTier: ProductTierType.Families, + name: "Families", + isAnnual: true, + upgradeSortOrder: 2, + displaySortOrder: 2, + PasswordManager: { + basePrice: 40, + seatPrice: 0, + maxSeats: 6, + baseSeats: 6, + hasAdditionalSeatsOption: false, + hasAdditionalStorageOption: true, + hasPremiumAccessOption: false, + baseStorageGb: 1, + additionalStoragePricePerGb: 4, + }, + SecretsManager: null, + } as PlanResponse, + { + type: PlanType.TeamsAnnually, + productTier: ProductTierType.Teams, + name: "Teams", + isAnnual: true, + canBeUsedByBusiness: true, + upgradeSortOrder: 3, + displaySortOrder: 3, + PasswordManager: { + basePrice: 0, + seatPrice: 48, + hasAdditionalSeatsOption: true, + hasAdditionalStorageOption: true, + hasPremiumAccessOption: true, + baseStorageGb: 1, + additionalStoragePricePerGb: 4, + premiumAccessOptionPrice: 40, + }, + SecretsManager: { + basePrice: 0, + seatPrice: 72, + hasAdditionalSeatsOption: true, + hasAdditionalServiceAccountOption: true, + baseServiceAccount: 50, + additionalPricePerServiceAccount: 6, + }, + } as PlanResponse, + { + type: PlanType.EnterpriseAnnually, + productTier: ProductTierType.Enterprise, + name: "Enterprise", + isAnnual: true, + canBeUsedByBusiness: true, + trialPeriodDays: 7, + upgradeSortOrder: 4, + displaySortOrder: 4, + PasswordManager: { + basePrice: 0, + seatPrice: 72, + hasAdditionalSeatsOption: true, + hasAdditionalStorageOption: true, + hasPremiumAccessOption: true, + baseStorageGb: 1, + additionalStoragePricePerGb: 4, + premiumAccessOptionPrice: 40, + }, + SecretsManager: { + basePrice: 0, + seatPrice: 144, + hasAdditionalSeatsOption: true, + hasAdditionalServiceAccountOption: true, + baseServiceAccount: 200, + additionalPricePerServiceAccount: 6, + }, + } as PlanResponse, + ]; +}; + +describe("OrganizationPlansComponent", () => { + let component: OrganizationPlansComponent; + let fixture: ComponentFixture; + + // Mock services + let mockApiService: jest.Mocked; + let mockI18nService: jest.Mocked; + let mockPlatformUtilsService: jest.Mocked; + let mockKeyService: jest.Mocked; + let mockEncryptService: jest.Mocked; + let mockRouter: jest.Mocked; + let mockSyncService: jest.Mocked; + let mockPolicyService: jest.Mocked; + let mockOrganizationService: jest.Mocked; + let mockMessagingService: jest.Mocked; + let mockOrganizationApiService: jest.Mocked; + let mockProviderApiService: jest.Mocked; + let mockToastService: jest.Mocked; + let mockAccountService: jest.Mocked; + let mockSubscriberBillingClient: jest.Mocked; + let mockPreviewInvoiceClient: jest.Mocked; + let mockConfigService: jest.Mocked; + + // Mock data + let mockPasswordManagerPlans: PlanResponse[]; + let mockOrganization: Organization; + let activeAccountSubject: BehaviorSubject; + let organizationsSubject: BehaviorSubject; + + beforeEach(async () => { + jest.clearAllMocks(); + + // Mock the static getFormGroup methods to return forms without validators + jest + .spyOn(EnterPaymentMethodComponent, "getFormGroup") + .mockReturnValue(MockEnterPaymentMethodComponent.getFormGroup() as any); + jest + .spyOn(EnterBillingAddressComponent, "getFormGroup") + .mockReturnValue(MockEnterBillingAddressComponent.getFormGroup() as any); + + // Initialize mock services + mockApiService = { + getPlans: jest.fn(), + postProviderCreateOrganization: jest.fn(), + refreshIdentityToken: jest.fn(), + } as any; + + mockI18nService = { + t: jest.fn((key: string) => key), + } as any; + + mockPlatformUtilsService = { + isSelfHost: jest.fn().mockReturnValue(false), + } as any; + + mockKeyService = { + makeOrgKey: jest.fn(), + makeKeyPair: jest.fn(), + orgKeys$: jest.fn().mockReturnValue(of({})), + providerKeys$: jest.fn().mockReturnValue(of({})), + } as any; + + mockEncryptService = { + encryptString: jest.fn(), + wrapSymmetricKey: jest.fn(), + } as any; + + mockRouter = { + navigate: jest.fn(), + } as any; + + mockSyncService = { + fullSync: jest.fn().mockResolvedValue(undefined), + } as any; + + mockPolicyService = { + policyAppliesToUser$: jest.fn().mockReturnValue(of(false)), + } as any; + + // Setup subjects for observables + activeAccountSubject = new BehaviorSubject({ + id: "user-id", + email: "test@example.com", + }); + organizationsSubject = new BehaviorSubject([]); + + mockAccountService = { + activeAccount$: activeAccountSubject.asObservable(), + } as any; + + mockOrganizationService = { + organizations$: jest.fn().mockReturnValue(organizationsSubject.asObservable()), + } as any; + + mockMessagingService = { + send: jest.fn(), + } as any; + + mockOrganizationApiService = { + getBilling: jest.fn(), + getSubscription: jest.fn(), + create: jest.fn(), + createLicense: jest.fn(), + upgrade: jest.fn(), + updateKeys: jest.fn(), + } as any; + + mockProviderApiService = { + getProvider: jest.fn(), + } as any; + + mockToastService = { + showToast: jest.fn(), + } as any; + + mockSubscriberBillingClient = { + getBillingAddress: jest.fn().mockResolvedValue({ + country: "US", + postalCode: "12345", + }), + updatePaymentMethod: jest.fn().mockResolvedValue(undefined), + } as any; + + mockPreviewInvoiceClient = { + previewTaxForOrganizationSubscriptionPurchase: jest.fn().mockResolvedValue({ + tax: 5.0, + total: 50.0, + }), + } as any; + + mockConfigService = { + getFeatureFlag: jest.fn().mockResolvedValue(true), + } as any; + + // Setup mock plan data + mockPasswordManagerPlans = createMockPlans(); + + mockApiService.getPlans.mockResolvedValue({ + data: mockPasswordManagerPlans, + } as any); + + await TestBed.configureTestingModule({ + providers: [ + { provide: ApiService, useValue: mockApiService }, + { provide: I18nService, useValue: mockI18nService }, + { provide: PlatformUtilsService, useValue: mockPlatformUtilsService }, + { provide: KeyService, useValue: mockKeyService }, + { provide: EncryptService, useValue: mockEncryptService }, + { provide: Router, useValue: mockRouter }, + { provide: SyncService, useValue: mockSyncService }, + { provide: PolicyService, useValue: mockPolicyService }, + { provide: OrganizationService, useValue: mockOrganizationService }, + { provide: MessagingService, useValue: mockMessagingService }, + FormBuilder, // Use real FormBuilder + { provide: OrganizationApiServiceAbstraction, useValue: mockOrganizationApiService }, + { provide: ProviderApiServiceAbstraction, useValue: mockProviderApiService }, + { provide: ToastService, useValue: mockToastService }, + { provide: AccountService, useValue: mockAccountService }, + { provide: SubscriberBillingClient, useValue: mockSubscriberBillingClient }, + { provide: PreviewInvoiceClient, useValue: mockPreviewInvoiceClient }, + { provide: ConfigService, useValue: mockConfigService }, + ], + }) + // Override the component to replace child components with mocks and provide mock services + .overrideComponent(OrganizationPlansComponent, { + remove: { + imports: [ + OrganizationInformationComponent, + SecretsManagerSubscribeComponent, + EnterPaymentMethodComponent, + EnterBillingAddressComponent, + OrganizationSelfHostingLicenseUploaderComponent, + ], + providers: [PreviewInvoiceClient, SubscriberBillingClient], + }, + add: { + imports: [ + MockOrgInfoComponent, + MockSmSubscribeComponent, + MockEnterPaymentMethodComponent, + MockEnterBillingAddressComponent, + MockOrganizationSelfHostingLicenseUploaderComponent, + ], + providers: [ + { provide: PreviewInvoiceClient, useValue: mockPreviewInvoiceClient }, + { provide: SubscriberBillingClient, useValue: mockSubscriberBillingClient }, + ], + }, + }) + .compileComponents(); + + fixture = TestBed.createComponent(OrganizationPlansComponent); + component = fixture.componentInstance; + }); + + describe("component creation", () => { + it("should create", () => { + expect(component).toBeTruthy(); + }); + + it("should initialize with default values", () => { + expect(component.loading).toBe(true); + expect(component.showFree).toBe(true); + expect(component.showCancel).toBe(false); + expect(component.productTier).toBe(ProductTierType.Free); + }); + }); + + describe("ngOnInit", () => { + describe("create organization flow", () => { + it("should load plans from API", async () => { + fixture.detectChanges(); + await fixture.whenStable(); + + expect(mockApiService.getPlans).toHaveBeenCalled(); + expect(component.passwordManagerPlans).toEqual(mockPasswordManagerPlans); + expect(component.loading).toBe(false); + }); + + it("should set required validators on name and billing email", async () => { + fixture.detectChanges(); + await fixture.whenStable(); + + component.formGroup.controls.name.setValue(""); + component.formGroup.controls.billingEmail.setValue(""); + + expect(component.formGroup.controls.name.hasError("required")).toBe(true); + expect(component.formGroup.controls.billingEmail.hasError("required")).toBe(true); + }); + + it("should not load organization data for create flow", async () => { + fixture.detectChanges(); + await fixture.whenStable(); + + expect(mockOrganizationApiService.getBilling).not.toHaveBeenCalled(); + expect(mockOrganizationApiService.getSubscription).not.toHaveBeenCalled(); + }); + }); + + describe("upgrade organization flow", () => { + beforeEach(() => { + mockOrganization = setupMockUpgradeOrganization( + mockOrganizationApiService, + organizationsSubject, + { + planType: PlanType.FamiliesAnnually2025, + }, + ); + + component.organizationId = mockOrganization.id; + }); + + it("should load existing organization data", async () => { + fixture.detectChanges(); + await fixture.whenStable(); + + expect(component.organization).toEqual(mockOrganization); + expect(mockOrganizationApiService.getBilling).toHaveBeenCalledWith(mockOrganization.id); + expect(mockOrganizationApiService.getSubscription).toHaveBeenCalledWith( + mockOrganization.id, + ); + expect(mockSubscriberBillingClient.getBillingAddress).toHaveBeenCalledWith({ + type: "organization", + data: mockOrganization, + }); + // Verify the form was updated + expect(component.billingFormGroup.controls.billingAddress.value.country).toBe("US"); + expect(component.billingFormGroup.controls.billingAddress.value.postalCode).toBe("12345"); + }); + + it("should not add validators for name and billingEmail in upgrade flow", async () => { + fixture.detectChanges(); + await fixture.whenStable(); + + component.formGroup.controls.name.setValue(""); + component.formGroup.controls.billingEmail.setValue(""); + + // In upgrade flow, these should not be required + expect(component.formGroup.controls.name.hasError("required")).toBe(false); + expect(component.formGroup.controls.billingEmail.hasError("required")).toBe(false); + }); + }); + + describe("feature flags", () => { + it("should use FamiliesAnnually when PM26462_Milestone_3 is enabled", async () => { + mockConfigService.getFeatureFlag.mockResolvedValue(true); + + fixture.detectChanges(); + await fixture.whenStable(); + + const familyPlan = component["_familyPlan"]; + expect(familyPlan).toBe(PlanType.FamiliesAnnually); + }); + + it("should use FamiliesAnnually2025 when feature flag is disabled", async () => { + mockConfigService.getFeatureFlag.mockResolvedValue(false); + + fixture.detectChanges(); + await fixture.whenStable(); + + const familyPlan = component["_familyPlan"]; + expect(familyPlan).toBe(PlanType.FamiliesAnnually2025); + }); + }); + }); + + describe("organization creation validation flow", () => { + beforeEach(async () => { + fixture.detectChanges(); + await fixture.whenStable(); + }); + + it("should prevent submission with invalid form data", async () => { + component.formGroup.patchValue({ + name: "", + billingEmail: "invalid-email", + additionalStorage: -1, + additionalSeats: 200000, + }); + + await component.submit(); + + expect(mockOrganizationApiService.create).not.toHaveBeenCalled(); + expect(component.formGroup.invalid).toBe(true); + }); + + it("should allow submission with valid form data", async () => { + patchOrganizationForm(component, { + name: "Valid Organization", + billingEmail: "valid@example.com", + productTier: ProductTierType.Free, + plan: PlanType.Free, + }); + + setupMockEncryptionKeys(mockKeyService, mockEncryptService); + mockOrganizationApiService.create.mockResolvedValue({ + id: "new-org-id", + } as any); + + await component.submit(); + + expect(mockOrganizationApiService.create).toHaveBeenCalled(); + }); + }); + + describe("plan selection flow", () => { + beforeEach(async () => { + fixture.detectChanges(); + await fixture.whenStable(); + }); + + it("should configure form appropriately when switching between product tiers", () => { + // Start with Families plan with unsupported features + component.productTier = ProductTierType.Families; + component.formGroup.controls.additionalSeats.setValue(10); + component.formGroup.controls.additionalStorage.setValue(5); + component.changedProduct(); + + // Families doesn't support additional seats + expect(component.formGroup.controls.additionalSeats.value).toBe(0); + expect(component.formGroup.controls.plan.value).toBe(PlanType.FamiliesAnnually); + + // Switch to Teams plan which supports additional seats + component.productTier = ProductTierType.Teams; + component.changedProduct(); + + expect(component.formGroup.controls.plan.value).toBe(PlanType.TeamsAnnually); + // Teams initializes with 1 seat by default + expect(component.formGroup.controls.additionalSeats.value).toBeGreaterThan(0); + + // Switch to Free plan which doesn't support additional storage + component.formGroup.controls.additionalStorage.setValue(10); + component.productTier = ProductTierType.Free; + component.changedProduct(); + + expect(component.formGroup.controls.additionalStorage.value).toBe(0); + }); + }); + + describe("subscription pricing flow", () => { + beforeEach(async () => { + fixture.detectChanges(); + await fixture.whenStable(); + }); + + it("should calculate total price based on selected plan options", () => { + // Select Teams plan and configure options + component.productTier = ProductTierType.Teams; + component.changedProduct(); + component.formGroup.controls.additionalSeats.setValue(5); + component.formGroup.controls.additionalStorage.setValue(10); + component.formGroup.controls.premiumAccessAddon.setValue(true); + + const pmSubtotal = component.passwordManagerSubtotal; + // Verify pricing includes all selected options + expect(pmSubtotal).toBeGreaterThan(0); + expect(pmSubtotal).toBe(5 * 48 + 10 * 4 + 40); // seats + storage + premium + }); + + it("should calculate pricing with Secrets Manager addon", () => { + component.productTier = ProductTierType.Teams; + component.plan = PlanType.TeamsAnnually; + + // Enable Secrets Manager with additional options + component.secretsManagerForm.patchValue({ + enabled: true, + userSeats: 3, + additionalServiceAccounts: 10, + }); + + const smSubtotal = component.secretsManagerSubtotal; + expect(smSubtotal).toBeGreaterThan(0); + + // Disable Secrets Manager + component.secretsManagerForm.patchValue({ + enabled: false, + }); + + expect(component.secretsManagerSubtotal).toBe(0); + }); + }); + + describe("tax calculation", () => { + beforeEach(async () => { + fixture.detectChanges(); + await fixture.whenStable(); + }); + + it("should calculate tax after debounce period", fakeAsync(() => { + component.productTier = ProductTierType.Teams; + component.changedProduct(); + component.formGroup.controls.additionalSeats.setValue(1); + component.billingFormGroup.controls.billingAddress.patchValue({ + country: "US", + postalCode: "12345", + }); + + tick(1500); // Wait for debounce (1000ms) + + expect( + mockPreviewInvoiceClient.previewTaxForOrganizationSubscriptionPurchase, + ).toHaveBeenCalled(); + expect(component["estimatedTax"]).toBe(5.0); + })); + + it("should not calculate tax with invalid billing address", fakeAsync(() => { + component.billingFormGroup.controls.billingAddress.patchValue({ + country: "", + postalCode: "", + }); + + tick(1500); + + expect( + mockPreviewInvoiceClient.previewTaxForOrganizationSubscriptionPurchase, + ).not.toHaveBeenCalled(); + })); + }); + + describe("submit", () => { + beforeEach(async () => { + fixture.detectChanges(); + await fixture.whenStable(); + }); + + it("should create organization successfully", async () => { + patchOrganizationForm(component, { + name: "New Org", + billingEmail: "test@example.com", + }); + + setupMockEncryptionKeys(mockKeyService, mockEncryptService); + mockOrganizationApiService.create.mockResolvedValue({ + id: "new-org-id", + } as any); + + await component.submit(); + + expect(mockOrganizationApiService.create).toHaveBeenCalled(); + expect(mockToastService.showToast).toHaveBeenCalledWith({ + variant: "success", + title: "organizationCreated", + message: "organizationReadyToGo", + }); + expect(mockSyncService.fullSync).toHaveBeenCalledWith(true); + }); + + it("should emit onSuccess after successful creation", async () => { + const onSuccessSpy = jest.spyOn(component.onSuccess, "emit"); + + patchOrganizationForm(component, { + name: "New Org", + billingEmail: "test@example.com", + }); + + setupMockEncryptionKeys(mockKeyService, mockEncryptService); + mockOrganizationApiService.create.mockResolvedValue({ + id: "new-org-id", + } as any); + + await component.submit(); + + expect(onSuccessSpy).toHaveBeenCalledWith({ + organizationId: "new-org-id", + }); + }); + + it("should handle payment method validation failure", async () => { + patchOrganizationForm(component, { + name: "New Org", + billingEmail: "test@example.com", + productTier: ProductTierType.Teams, + plan: PlanType.TeamsAnnually, + additionalSeats: 5, + }); + + patchBillingAddress(component); + setupMockEncryptionKeys(mockKeyService, mockEncryptService); + + // Mock payment method component to return null (failure) + component["enterPaymentMethodComponent"] = { + tokenize: jest.fn().mockResolvedValue(null), + } as any; + + await component.submit(); + + // Should not create organization if payment method validation fails + expect(mockOrganizationApiService.create).not.toHaveBeenCalled(); + }); + + it("should block submission when single org policy applies", async () => { + mockPolicyService.policyAppliesToUser$.mockReturnValue(of(true)); + + // Need to reinitialize after changing policy mock + const policyFixture = TestBed.createComponent(OrganizationPlansComponent); + const policyComponent = policyFixture.componentInstance; + policyFixture.detectChanges(); + await policyFixture.whenStable(); + + policyComponent.formGroup.patchValue({ + name: "Test", + billingEmail: "test@example.com", + }); + + await policyComponent.submit(); + + expect(mockOrganizationApiService.create).not.toHaveBeenCalled(); + }); + }); + + describe("provider flow", () => { + beforeEach(() => { + component.providerId = "provider-123"; + }); + + it("should load provider data", async () => { + mockProviderApiService.getProvider.mockResolvedValue({ + id: "provider-123", + name: "Test Provider", + } as any); + + fixture.detectChanges(); + await fixture.whenStable(); + + expect(mockProviderApiService.getProvider).toHaveBeenCalledWith("provider-123"); + expect(component.provider).toBeDefined(); + }); + + it("should default to Teams Annual plan for providers", async () => { + mockProviderApiService.getProvider.mockResolvedValue({} as any); + + fixture.detectChanges(); + await fixture.whenStable(); + + expect(component.plan).toBe(PlanType.TeamsAnnually); + }); + + it("should require clientOwnerEmail for provider flow", async () => { + mockProviderApiService.getProvider.mockResolvedValue({} as any); + + fixture.detectChanges(); + await fixture.whenStable(); + + const clientOwnerEmailControl = component.formGroup.controls.clientOwnerEmail; + clientOwnerEmailControl.setValue(""); + + expect(clientOwnerEmailControl.hasError("required")).toBe(true); + }); + + it("should set businessOwned to true for provider flow", async () => { + mockProviderApiService.getProvider.mockResolvedValue({} as any); + + fixture.detectChanges(); + await fixture.whenStable(); + + expect(component.formGroup.controls.businessOwned.value).toBe(true); + }); + }); + + describe("self-hosted flow", () => { + beforeEach(async () => { + mockPlatformUtilsService.isSelfHost.mockReturnValue(true); + }); + + it("should render organization self-hosted license and not load plans", async () => { + mockPlatformUtilsService.isSelfHost.mockReturnValue(true); + const selfHostedFixture = TestBed.createComponent(OrganizationPlansComponent); + const selfHostedComponent = selfHostedFixture.componentInstance; + + expect(selfHostedComponent.selfHosted).toBe(true); + expect(mockApiService.getPlans).not.toHaveBeenCalled(); + }); + + it("should handle license file upload success", async () => { + const successSpy = jest.spyOn(component.onSuccess, "emit"); + + await component["onLicenseFileUploaded"]("uploaded-org-id"); + + expect(mockToastService.showToast).toHaveBeenCalledWith({ + variant: "success", + title: "organizationCreated", + message: "organizationReadyToGo", + }); + + expect(successSpy).toHaveBeenCalledWith({ + organizationId: "uploaded-org-id", + }); + + expect(mockMessagingService.send).toHaveBeenCalledWith("organizationCreated", { + organizationId: "uploaded-org-id", + }); + }); + + it("should navigate after license upload if not in trial or sponsorship flow", async () => { + component.acceptingSponsorship = false; + component["isInTrialFlow"] = false; + + await component["onLicenseFileUploaded"]("uploaded-org-id"); + + expect(mockRouter.navigate).toHaveBeenCalledWith(["/organizations/uploaded-org-id"]); + }); + + it("should not navigate after license upload if accepting sponsorship", async () => { + component.acceptingSponsorship = true; + + await component["onLicenseFileUploaded"]("uploaded-org-id"); + + expect(mockRouter.navigate).not.toHaveBeenCalled(); + }); + + it("should emit trial success after license upload in trial flow", async () => { + component["isInTrialFlow"] = true; + + fixture.detectChanges(); + await fixture.whenStable(); + + const trialSpy = jest.spyOn(component.onTrialBillingSuccess, "emit"); + + await component["onLicenseFileUploaded"]("uploaded-org-id"); + + expect(trialSpy).toHaveBeenCalled(); + }); + }); + + describe("policy enforcement", () => { + it("should check single org policy", async () => { + mockPolicyService.policyAppliesToUser$.mockReturnValue(of(true)); + + fixture.detectChanges(); + await fixture.whenStable(); + + expect(component.singleOrgPolicyAppliesToActiveUser).toBe(true); + }); + + it("should not block provider flow with single org policy", async () => { + mockPolicyService.policyAppliesToUser$.mockReturnValue(of(true)); + component.providerId = "provider-123"; + mockProviderApiService.getProvider.mockResolvedValue({} as any); + + fixture.detectChanges(); + await fixture.whenStable(); + + expect(component.singleOrgPolicyBlock).toBe(false); + }); + }); + + describe("business ownership change flow", () => { + beforeEach(async () => { + fixture.detectChanges(); + await fixture.whenStable(); + }); + + it("should automatically upgrade to business-compatible plan when marking as business-owned", () => { + // Start with a personal plan + component.formGroup.controls.businessOwned.setValue(false); + component.productTier = ProductTierType.Families; + component.plan = PlanType.FamiliesAnnually; + + // Mark as business-owned + component.formGroup.controls.businessOwned.setValue(true); + component.changedOwnedBusiness(); + + // Should automatically switch to Teams (lowest business plan) + expect(component.formGroup.controls.productTier.value).toBe(ProductTierType.Teams); + expect(component.formGroup.controls.plan.value).toBe(PlanType.TeamsAnnually); + + // Unchecking businessOwned should not force a downgrade + component.formGroup.controls.businessOwned.setValue(false); + component.changedOwnedBusiness(); + + expect(component.formGroup.controls.productTier.value).toBe(ProductTierType.Teams); + }); + }); + + describe("business organization plan selection flow", () => { + beforeEach(async () => { + fixture.detectChanges(); + await fixture.whenStable(); + }); + + it("should restrict available plans based on business ownership and upgrade context", () => { + // Upgrade flow (showFree = false) should exclude Free plan + component.showFree = false; + let products = component.selectableProducts; + expect(products.find((p) => p.type === PlanType.Free)).toBeUndefined(); + + // Create flow (showFree = true) should include Free plan + component.showFree = true; + products = component.selectableProducts; + expect(products.find((p) => p.type === PlanType.Free)).toBeDefined(); + + // Business organizations should only see business-compatible plans + component.formGroup.controls.businessOwned.setValue(true); + products = component.selectableProducts; + const nonFreeBusinessPlans = products.filter((p) => p.type !== PlanType.Free); + nonFreeBusinessPlans.forEach((plan) => { + expect(plan.canBeUsedByBusiness).toBe(true); + }); + }); + }); + + describe("accepting sponsorship flow", () => { + beforeEach(() => { + component.acceptingSponsorship = true; + }); + + it("should configure Families plan with full discount when accepting sponsorship", async () => { + fixture.detectChanges(); + await fixture.whenStable(); + + // Only Families plan should be available + const products = component.selectableProducts; + expect(products.length).toBe(1); + expect(products[0].productTier).toBe(ProductTierType.Families); + + // Full discount should be applied making the base price free + component.productTier = ProductTierType.Families; + component.plan = PlanType.FamiliesAnnually; + + const subtotal = component.passwordManagerSubtotal; + expect(subtotal).toBe(0); // Discount covers the full base price + expect(component.discount).toBe(products[0].PasswordManager.basePrice); + }); + }); + + describe("upgrade flow", () => { + it("should successfully upgrade organization", async () => { + setupMockUpgradeOrganization(mockOrganizationApiService, organizationsSubject, { + maxStorageGb: 2, + }); + + const upgradeFixture = TestBed.createComponent(OrganizationPlansComponent); + const upgradeComponent = upgradeFixture.componentInstance; + upgradeComponent.organizationId = "org-123"; + upgradeComponent.currentPlan = mockPasswordManagerPlans[0]; // Free plan + + upgradeFixture.detectChanges(); + await upgradeFixture.whenStable(); + + upgradeComponent.productTier = ProductTierType.Teams; + upgradeComponent.plan = PlanType.TeamsAnnually; + upgradeComponent.formGroup.controls.additionalSeats.setValue(5); + + mockOrganizationApiService.upgrade.mockResolvedValue(undefined); + + await upgradeComponent.submit(); + + expect(mockOrganizationApiService.upgrade).toHaveBeenCalledWith( + "org-123", + expect.objectContaining({ + planType: PlanType.TeamsAnnually, + additionalSeats: 5, + }), + ); + + expect(mockToastService.showToast).toHaveBeenCalledWith({ + variant: "success", + title: null, + message: "organizationUpgraded", + }); + }); + + it("should handle upgrade requiring payment method", async () => { + setupMockUpgradeOrganization(mockOrganizationApiService, organizationsSubject, { + hasPaymentSource: false, + maxStorageGb: 2, + }); + + const upgradeFixture = TestBed.createComponent(OrganizationPlansComponent); + const upgradeComponent = upgradeFixture.componentInstance; + upgradeComponent.organizationId = "org-123"; + upgradeComponent.showFree = false; // Required for upgradeRequiresPaymentMethod + + upgradeFixture.detectChanges(); + await upgradeFixture.whenStable(); + + expect(upgradeComponent.upgradeRequiresPaymentMethod).toBe(true); + }); + }); + + describe("billing form display flow", () => { + beforeEach(async () => { + fixture.detectChanges(); + await fixture.whenStable(); + }); + + it("should show appropriate billing fields based on plan type", () => { + // Personal plans (Free, Families) should not require tax ID + component.productTier = ProductTierType.Free; + expect(component["showTaxIdField"]).toBe(false); + + component.productTier = ProductTierType.Families; + expect(component["showTaxIdField"]).toBe(false); + + // Business plans (Teams, Enterprise) should show tax ID field + component.productTier = ProductTierType.Teams; + expect(component["showTaxIdField"]).toBe(true); + + component.productTier = ProductTierType.Enterprise; + expect(component["showTaxIdField"]).toBe(true); + }); + }); + + describe("secrets manager handling flow", () => { + beforeEach(async () => { + fixture.detectChanges(); + await fixture.whenStable(); + }); + + it("should prefill SM seats from existing subscription", async () => { + mockOrganization = { + id: "org-123", + name: "Test Org", + productTierType: ProductTierType.Teams, + useSecretsManager: true, + } as Organization; + + organizationsSubject.next([mockOrganization]); + + mockOrganizationApiService.getBilling.mockResolvedValue({ + paymentSource: { type: "card" }, + } as any); + + mockOrganizationApiService.getSubscription.mockResolvedValue({ + planType: PlanType.TeamsAnnually, + smSeats: 5, + smServiceAccounts: 75, + } as any); + + const upgradeFixture = TestBed.createComponent(OrganizationPlansComponent); + const upgradeComponent = upgradeFixture.componentInstance; + upgradeComponent.organizationId = "org-123"; + upgradeComponent.currentPlan = mockPasswordManagerPlans[2]; // Teams plan + + upgradeFixture.detectChanges(); + await upgradeFixture.whenStable(); + + upgradeComponent.changedProduct(); + + expect(upgradeComponent.secretsManagerForm.controls.enabled.value).toBe(true); + expect(upgradeComponent.secretsManagerForm.controls.userSeats.value).toBe(5); + expect(upgradeComponent.secretsManagerForm.controls.additionalServiceAccounts.value).toBe(25); + }); + + it("should enable SM by default when enableSecretsManagerByDefault is true", async () => { + const smFixture = TestBed.createComponent(OrganizationPlansComponent); + const smComponent = smFixture.componentInstance; + smComponent.enableSecretsManagerByDefault = true; + smComponent.productTier = ProductTierType.Teams; + + smFixture.detectChanges(); + await smFixture.whenStable(); + + expect(smComponent.secretsManagerForm.value.enabled).toBe(true); + expect(smComponent.secretsManagerForm.value.userSeats).toBe(1); + expect(smComponent.secretsManagerForm.value.additionalServiceAccounts).toBe(0); + }); + + it("should trigger tax recalculation when SM form changes", fakeAsync(() => { + component.productTier = ProductTierType.Teams; + component.changedProduct(); + component.billingFormGroup.controls.billingAddress.patchValue({ + country: "US", + postalCode: "90210", + }); + + // Clear previous calls + jest.clearAllMocks(); + + // Change SM form + component.secretsManagerForm.patchValue({ + enabled: true, + userSeats: 3, + }); + + tick(1500); // Wait for debounce + + expect( + mockPreviewInvoiceClient.previewTaxForOrganizationSubscriptionPurchase, + ).toHaveBeenCalled(); + })); + }); + + describe("form update helpers flow", () => { + beforeEach(async () => { + fixture.detectChanges(); + await fixture.whenStable(); + }); + + it("should handle premium addon access based on plan features", () => { + // Plan without premium access option should set addon to true (meaning it's included) + component.productTier = ProductTierType.Families; + component.changedProduct(); + + expect(component.formGroup.controls.premiumAccessAddon.value).toBe(true); + + // Plan with premium access option should set addon to false (user can opt-in) + component.productTier = ProductTierType.Teams; + component.changedProduct(); + + expect(component.formGroup.controls.premiumAccessAddon.value).toBe(false); + }); + + it("should handle additional storage for upgrade with existing data", async () => { + mockOrganization = { + id: "org-123", + name: "Test Org", + productTierType: ProductTierType.Free, + maxStorageGb: 5, + } as Organization; + + organizationsSubject.next([mockOrganization]); + + mockOrganizationApiService.getBilling.mockResolvedValue({ + paymentSource: { type: "card" }, + } as any); + + mockOrganizationApiService.getSubscription.mockResolvedValue({ + planType: PlanType.Free, + } as any); + + const upgradeFixture = TestBed.createComponent(OrganizationPlansComponent); + const upgradeComponent = upgradeFixture.componentInstance; + upgradeComponent.organizationId = "org-123"; + upgradeComponent.currentPlan = mockPasswordManagerPlans[0]; // Free plan with 0 GB base + + upgradeFixture.detectChanges(); + await upgradeFixture.whenStable(); + + upgradeComponent.productTier = ProductTierType.Teams; + upgradeComponent.changedProduct(); + + expect(upgradeComponent.formGroup.controls.additionalStorage.value).toBe(5); + }); + + it("should reset additional storage when plan doesn't support it", () => { + component.formGroup.controls.additionalStorage.setValue(10); + component.productTier = ProductTierType.Free; + component.changedProduct(); + + expect(component.formGroup.controls.additionalStorage.value).toBe(0); + }); + + it("should handle additional seats for various scenarios", () => { + // Plan without additional seats option should reset to 0 + component.formGroup.controls.additionalSeats.setValue(10); + component.productTier = ProductTierType.Families; + component.changedProduct(); + + expect(component.formGroup.controls.additionalSeats.value).toBe(0); + + // Default to 1 seat for new org with seats option + component.productTier = ProductTierType.Teams; + component.changedProduct(); + + expect(component.formGroup.controls.additionalSeats.value).toBeGreaterThanOrEqual(1); + }); + + it("should prefill seats from current plan when upgrading from non-seats plan", async () => { + mockOrganization = { + id: "org-123", + name: "Test Org", + productTierType: ProductTierType.Free, + seats: 2, + } as Organization; + + organizationsSubject.next([mockOrganization]); + + mockOrganizationApiService.getBilling.mockResolvedValue({ + paymentSource: { type: "card" }, + } as any); + + mockOrganizationApiService.getSubscription.mockResolvedValue({ + planType: PlanType.Free, + } as any); + + const upgradeFixture = TestBed.createComponent(OrganizationPlansComponent); + const upgradeComponent = upgradeFixture.componentInstance; + upgradeComponent.organizationId = "org-123"; + upgradeComponent.currentPlan = mockPasswordManagerPlans[0]; // Free plan (no additional seats) + + upgradeFixture.detectChanges(); + await upgradeFixture.whenStable(); + + upgradeComponent.productTier = ProductTierType.Teams; + upgradeComponent.changedProduct(); + + // Should use base seats from current plan + expect(upgradeComponent.formGroup.controls.additionalSeats.value).toBe(2); + }); + }); + + describe("provider creation flow", () => { + beforeEach(() => { + component.providerId = "provider-123"; + mockProviderApiService.getProvider.mockResolvedValue({ + id: "provider-123", + name: "Test Provider", + } as any); + }); + + it("should create organization through provider with wrapped key", async () => { + fixture.detectChanges(); + await fixture.whenStable(); + + patchOrganizationForm(component, { + name: "Provider Client Org", + billingEmail: "client@example.com", + productTier: ProductTierType.Teams, + plan: PlanType.TeamsAnnually, + additionalSeats: 5, + }); + component.formGroup.patchValue({ + clientOwnerEmail: "owner@client.com", + }); + + patchBillingAddress(component); + + const mockOrgKey = {} as any; + const mockProviderKey = {} as any; + + mockKeyService.makeOrgKey.mockResolvedValue([ + { encryptedString: "mock-key" }, + mockOrgKey, + ] as any); + + mockEncryptService.encryptString.mockResolvedValue({ + encryptedString: "mock-collection", + } as any); + + mockKeyService.makeKeyPair.mockResolvedValue([ + "public-key", + { encryptedString: "private-key" }, + ] as any); + + mockKeyService.providerKeys$.mockReturnValue(of({ "provider-123": mockProviderKey })); + + mockEncryptService.wrapSymmetricKey.mockResolvedValue({ + encryptedString: "wrapped-key", + } as any); + + mockApiService.postProviderCreateOrganization.mockResolvedValue({ + organizationId: "provider-org-id", + } as any); + + setupMockPaymentMethodComponent(component); + + await component.submit(); + + expect(mockApiService.postProviderCreateOrganization).toHaveBeenCalledWith( + "provider-123", + expect.objectContaining({ + clientOwnerEmail: "owner@client.com", + }), + ); + + expect(mockEncryptService.wrapSymmetricKey).toHaveBeenCalledWith(mockOrgKey, mockProviderKey); + }); + }); + + describe("upgrade with missing keys flow", () => { + beforeEach(async () => { + mockOrganization = { + id: "org-123", + name: "Test Org", + productTierType: ProductTierType.Free, + seats: 5, + hasPublicAndPrivateKeys: false, // Missing keys + } as Organization; + + organizationsSubject.next([mockOrganization]); + + mockOrganizationApiService.getBilling.mockResolvedValue({ + paymentSource: { type: "card" }, + } as any); + + mockOrganizationApiService.getSubscription.mockResolvedValue({ + planType: PlanType.Free, + } as any); + + component.organizationId = "org-123"; + + fixture.detectChanges(); + await fixture.whenStable(); + }); + + it("should backfill organization keys during upgrade", async () => { + component.productTier = ProductTierType.Teams; + component.plan = PlanType.TeamsAnnually; + component.formGroup.controls.additionalSeats.setValue(5); + + const mockOrgShareKey = {} as any; + mockKeyService.orgKeys$.mockReturnValue(of({ "org-123": mockOrgShareKey })); + + mockKeyService.makeKeyPair.mockResolvedValue([ + "public-key", + { encryptedString: "private-key" }, + ] as any); + + mockOrganizationApiService.upgrade.mockResolvedValue(undefined); + + await component.submit(); + + expect(mockOrganizationApiService.upgrade).toHaveBeenCalledWith( + "org-123", + expect.objectContaining({ + keys: expect.any(Object), + }), + ); + }); + }); + + describe("trial flow", () => { + beforeEach(async () => { + fixture.detectChanges(); + await fixture.whenStable(); + }); + + it("should emit onTrialBillingSuccess when in trial flow", async () => { + component["isInTrialFlow"] = true; + const trialSpy = jest.spyOn(component.onTrialBillingSuccess, "emit"); + + component.formGroup.patchValue({ + name: "Trial Org", + billingEmail: "trial@example.com", + productTier: ProductTierType.Enterprise, + plan: PlanType.EnterpriseAnnually, + additionalSeats: 10, + }); + + component.billingFormGroup.controls.billingAddress.patchValue({ + country: "US", + postalCode: "12345", + line1: "123 Street", + city: "City", + state: "CA", + }); + + mockKeyService.makeOrgKey.mockResolvedValue([ + { encryptedString: "mock-key" }, + {} as any, + ] as any); + + mockEncryptService.encryptString.mockResolvedValue({ + encryptedString: "mock-collection", + } as any); + + mockKeyService.makeKeyPair.mockResolvedValue([ + "public-key", + { encryptedString: "private-key" }, + ] as any); + + mockOrganizationApiService.create.mockResolvedValue({ + id: "trial-org-id", + } as any); + + component["enterPaymentMethodComponent"] = { + tokenize: jest.fn().mockResolvedValue({ + token: "mock_token", + type: "card", + }), + } as any; + + await component.submit(); + + expect(trialSpy).toHaveBeenCalledWith({ + orgId: "trial-org-id", + subLabelText: expect.stringContaining("annual"), + }); + }); + + it("should not navigate away when in trial flow", async () => { + component["isInTrialFlow"] = true; + + component.formGroup.patchValue({ + name: "Trial Org", + billingEmail: "trial@example.com", + productTier: ProductTierType.Free, + plan: PlanType.Free, + }); + + mockKeyService.makeOrgKey.mockResolvedValue([ + { encryptedString: "mock-key" }, + {} as any, + ] as any); + + mockEncryptService.encryptString.mockResolvedValue({ + encryptedString: "mock-collection", + } as any); + + mockKeyService.makeKeyPair.mockResolvedValue([ + "public-key", + { encryptedString: "private-key" }, + ] as any); + + mockOrganizationApiService.create.mockResolvedValue({ + id: "trial-org-id", + } as any); + + await component.submit(); + + expect(mockRouter.navigate).not.toHaveBeenCalled(); + }); + }); + + describe("upgrade prefill flow", () => { + it("should prefill Families plan for Free tier upgrade", async () => { + mockOrganization = { + id: "org-123", + name: "Test Org", + productTierType: ProductTierType.Free, + } as Organization; + + organizationsSubject.next([mockOrganization]); + + mockOrganizationApiService.getBilling.mockResolvedValue({ + paymentSource: null, + } as any); + + mockOrganizationApiService.getSubscription.mockResolvedValue({ + planType: PlanType.Free, + } as any); + + const upgradeFixture = TestBed.createComponent(OrganizationPlansComponent); + const upgradeComponent = upgradeFixture.componentInstance; + upgradeComponent.organizationId = "org-123"; + upgradeComponent.currentPlan = mockPasswordManagerPlans[0]; // Free + + upgradeFixture.detectChanges(); + await upgradeFixture.whenStable(); + + expect(upgradeComponent.plan).toBe(PlanType.FamiliesAnnually); + expect(upgradeComponent.productTier).toBe(ProductTierType.Families); + }); + + it("should prefill Teams plan for Families tier upgrade when TeamsStarter unavailable", async () => { + mockOrganization = { + id: "org-123", + name: "Test Org", + productTierType: ProductTierType.Families, + } as Organization; + + organizationsSubject.next([mockOrganization]); + + mockOrganizationApiService.getBilling.mockResolvedValue({ + paymentSource: { type: "card" }, + } as any); + + mockOrganizationApiService.getSubscription.mockResolvedValue({ + planType: PlanType.FamiliesAnnually, + } as any); + + const upgradeFixture = TestBed.createComponent(OrganizationPlansComponent); + const upgradeComponent = upgradeFixture.componentInstance; + upgradeComponent.organizationId = "org-123"; + upgradeComponent.currentPlan = mockPasswordManagerPlans[1]; // Families + + upgradeFixture.detectChanges(); + await upgradeFixture.whenStable(); + + expect(upgradeComponent.plan).toBe(PlanType.TeamsAnnually); + expect(upgradeComponent.productTier).toBe(ProductTierType.Teams); + }); + + it("should use upgradeSortOrder for sequential plan upgrades", async () => { + mockOrganization = { + id: "org-123", + name: "Test Org", + productTierType: ProductTierType.Teams, + } as Organization; + + organizationsSubject.next([mockOrganization]); + + mockOrganizationApiService.getBilling.mockResolvedValue({ + paymentSource: { type: "card" }, + } as any); + + mockOrganizationApiService.getSubscription.mockResolvedValue({ + planType: PlanType.TeamsAnnually, + } as any); + + const upgradeFixture = TestBed.createComponent(OrganizationPlansComponent); + const upgradeComponent = upgradeFixture.componentInstance; + upgradeComponent.organizationId = "org-123"; + upgradeComponent.currentPlan = mockPasswordManagerPlans[2]; // Teams + + upgradeFixture.detectChanges(); + await upgradeFixture.whenStable(); + + expect(upgradeComponent.plan).toBe(PlanType.EnterpriseAnnually); + expect(upgradeComponent.productTier).toBe(ProductTierType.Enterprise); + }); + + it("should not prefill for Enterprise tier (no upgrade available)", async () => { + mockOrganization = { + id: "org-123", + name: "Test Org", + productTierType: ProductTierType.Enterprise, + } as Organization; + + organizationsSubject.next([mockOrganization]); + + mockOrganizationApiService.getBilling.mockResolvedValue({ + paymentSource: { type: "card" }, + } as any); + + mockOrganizationApiService.getSubscription.mockResolvedValue({ + planType: PlanType.EnterpriseAnnually, + } as any); + + const upgradeFixture = TestBed.createComponent(OrganizationPlansComponent); + const upgradeComponent = upgradeFixture.componentInstance; + upgradeComponent.organizationId = "org-123"; + upgradeComponent.currentPlan = mockPasswordManagerPlans[3]; // Enterprise + + upgradeFixture.detectChanges(); + await upgradeFixture.whenStable(); + + // Should not change from default Free + expect(upgradeComponent.productTier).toBe(ProductTierType.Free); + }); + }); + + describe("plan filtering logic", () => { + beforeEach(async () => { + fixture.detectChanges(); + await fixture.whenStable(); + }); + + it("should check if provider is qualified for 2020 plans", () => { + component.providerId = "provider-123"; + component["provider"] = { + id: "provider-123", + creationDate: "2023-01-01", // Before cutoff + } as any; + + const isQualified = component["isProviderQualifiedFor2020Plan"](); + + expect(isQualified).toBe(true); + }); + + it("should not qualify provider created after 2020 plan cutoff", () => { + component.providerId = "provider-123"; + component["provider"] = { + id: "provider-123", + creationDate: "2023-12-01", // After cutoff (2023-11-06) + } as any; + + const isQualified = component["isProviderQualifiedFor2020Plan"](); + + expect(isQualified).toBe(false); + }); + + it("should return false if provider has no creation date", () => { + component.providerId = "provider-123"; + component["provider"] = { + id: "provider-123", + creationDate: null, + } as any; + + const isQualified = component["isProviderQualifiedFor2020Plan"](); + + expect(isQualified).toBe(false); + }); + + it("should exclude upgrade-ineligible plans", async () => { + mockOrganization = { + id: "org-123", + name: "Test Org", + productTierType: ProductTierType.Teams, + } as Organization; + + organizationsSubject.next([mockOrganization]); + + mockOrganizationApiService.getBilling.mockResolvedValue({ + paymentSource: { type: "card" }, + } as any); + + mockOrganizationApiService.getSubscription.mockResolvedValue({ + planType: PlanType.TeamsAnnually, + } as any); + + const upgradeFixture = TestBed.createComponent(OrganizationPlansComponent); + const upgradeComponent = upgradeFixture.componentInstance; + upgradeComponent.organizationId = "org-123"; + upgradeComponent.currentPlan = mockPasswordManagerPlans[2]; // Teams + + upgradeFixture.detectChanges(); + await upgradeFixture.whenStable(); + + const products = upgradeComponent.selectableProducts; + + // Should not include plans with lower or equal upgradeSortOrder + expect(products.find((p) => p.type === PlanType.Free)).toBeUndefined(); + expect(products.find((p) => p.type === PlanType.FamiliesAnnually)).toBeUndefined(); + expect(products.find((p) => p.type === PlanType.TeamsAnnually)).toBeUndefined(); + }); + }); + + describe("helper calculation methods", () => { + beforeEach(async () => { + fixture.detectChanges(); + await fixture.whenStable(); + }); + + it("should calculate monthly seat price correctly", () => { + const annualPlan = mockPasswordManagerPlans[2]; // Teams Annual - 48/year + const monthlyPrice = component.seatPriceMonthly(annualPlan); + + expect(monthlyPrice).toBe(4); // 48 / 12 + }); + + it("should calculate monthly storage price correctly", () => { + const annualPlan = mockPasswordManagerPlans[2]; // 4/GB/year + const monthlyPrice = component.additionalStoragePriceMonthly(annualPlan); + + expect(monthlyPrice).toBeCloseTo(0.333, 2); // 4 / 12 + }); + + it("should generate billing sublabel text for annual plan", () => { + component.productTier = ProductTierType.Teams; + component.plan = PlanType.TeamsAnnually; + + const sublabel = component["billingSubLabelText"](); + + expect(sublabel).toContain("annual"); + expect(sublabel).toContain("$48"); // Seat price + expect(sublabel).toContain("yr"); + }); + + it("should generate billing sublabel text for plan with base price", () => { + component.productTier = ProductTierType.Families; + component.plan = PlanType.FamiliesAnnually; + + const sublabel = component["billingSubLabelText"](); + + expect(sublabel).toContain("annual"); + expect(sublabel).toContain("$40"); // Base price + }); + }); + + describe("template rendering and UI visibility", () => { + beforeEach(async () => { + fixture.detectChanges(); + await fixture.whenStable(); + }); + + it("should control form visibility based on loading state", () => { + // Initially not loading after setup + expect(component.loading).toBe(false); + + // When loading + component.loading = true; + expect(component.loading).toBe(true); + + // When not loading + component.loading = false; + expect(component.loading).toBe(false); + }); + + it("should determine createOrganization based on organizationId", () => { + // Create flow - no organizationId + expect(component.createOrganization).toBe(true); + + // Upgrade flow - has organizationId + component.organizationId = "org-123"; + expect(component.createOrganization).toBe(false); + }); + + it("should calculate passwordManagerSubtotal correctly for paid plans", () => { + component.productTier = ProductTierType.Teams; + component.changedProduct(); + component.formGroup.controls.additionalSeats.setValue(5); + + const subtotal = component.passwordManagerSubtotal; + + expect(typeof subtotal).toBe("number"); + expect(subtotal).toBeGreaterThan(0); + }); + + it("should show payment description based on plan type", () => { + component.productTier = ProductTierType.Teams; + component.changedProduct(); + + const paymentDesc = component.paymentDesc; + + expect(typeof paymentDesc).toBe("string"); + expect(paymentDesc.length).toBeGreaterThan(0); + }); + + it("should display tax ID field for business plans", () => { + component.productTier = ProductTierType.Free; + expect(component["showTaxIdField"]).toBe(false); + + component.productTier = ProductTierType.Families; + expect(component["showTaxIdField"]).toBe(false); + + component.productTier = ProductTierType.Teams; + expect(component["showTaxIdField"]).toBe(true); + + component.productTier = ProductTierType.Enterprise; + expect(component["showTaxIdField"]).toBe(true); + }); + + it("should show single org policy block when applicable", () => { + component.singleOrgPolicyAppliesToActiveUser = false; + expect(component.singleOrgPolicyBlock).toBe(false); + + component.singleOrgPolicyAppliesToActiveUser = true; + expect(component.singleOrgPolicyBlock).toBe(true); + + // But not when has provider + component.providerId = "provider-123"; + expect(component.singleOrgPolicyBlock).toBe(false); + }); + + it("should determine upgrade requires payment method correctly", async () => { + // Create flow - no organization + expect(component.upgradeRequiresPaymentMethod).toBe(false); + + // Create new component with organization setup + const mockOrg = setupMockUpgradeOrganization( + mockOrganizationApiService, + organizationsSubject, + { + productTierType: ProductTierType.Free, + hasPaymentSource: false, + }, + ); + + const upgradeFixture = TestBed.createComponent(OrganizationPlansComponent); + const upgradeComponent = upgradeFixture.componentInstance; + upgradeComponent.organizationId = mockOrg.id; + upgradeComponent.showFree = false; + + upgradeFixture.detectChanges(); + await upgradeFixture.whenStable(); + + expect(upgradeComponent.upgradeRequiresPaymentMethod).toBe(true); + }); + }); + + describe("user interactions and form controls", () => { + beforeEach(async () => { + fixture.detectChanges(); + await fixture.whenStable(); + }); + + it("should update component state when product tier changes", () => { + component.productTier = ProductTierType.Free; + + // Simulate changing product tier + component.productTier = ProductTierType.Teams; + component.formGroup.controls.productTier.setValue(ProductTierType.Teams); + component.changedProduct(); + + expect(component.productTier).toBe(ProductTierType.Teams); + expect(component.formGroup.controls.plan.value).toBe(PlanType.TeamsAnnually); + }); + + it("should update plan when changedOwnedBusiness is called", () => { + component.formGroup.controls.businessOwned.setValue(false); + component.productTier = ProductTierType.Families; + + component.formGroup.controls.businessOwned.setValue(true); + component.changedOwnedBusiness(); + + // Should switch to a business-compatible plan + expect(component.formGroup.controls.productTier.value).toBe(ProductTierType.Teams); + }); + + it("should emit onCanceled when cancel is called", () => { + const cancelSpy = jest.spyOn(component.onCanceled, "emit"); + + component["cancel"](); + + expect(cancelSpy).toHaveBeenCalled(); + }); + + it("should update form value when additional seats changes", () => { + component.productTier = ProductTierType.Teams; + component.changedProduct(); + + component.formGroup.controls.additionalSeats.setValue(10); + + expect(component.formGroup.controls.additionalSeats.value).toBe(10); + }); + + it("should update form value when additional storage changes", () => { + component.productTier = ProductTierType.Teams; + component.changedProduct(); + + component.formGroup.controls.additionalStorage.setValue(5); + + expect(component.formGroup.controls.additionalStorage.value).toBe(5); + }); + + it("should mark form as invalid when required fields are empty", () => { + component.formGroup.controls.name.setValue(""); + component.formGroup.controls.billingEmail.setValue(""); + component.formGroup.markAllAsTouched(); + + expect(component.formGroup.invalid).toBe(true); + }); + + it("should mark form as valid when all required fields are filled correctly", () => { + patchOrganizationForm(component, { + name: "Valid Org", + billingEmail: "valid@example.com", + }); + + expect(component.formGroup.valid).toBe(true); + }); + + it("should calculate subtotals based on form values", () => { + component.productTier = ProductTierType.Teams; + component.changedProduct(); + component.formGroup.controls.additionalSeats.setValue(5); + component.formGroup.controls.additionalStorage.setValue(10); + + const subtotal = component.passwordManagerSubtotal; + + // Should include cost of seats and storage + expect(subtotal).toBeGreaterThan(0); + }); + + it("should enable Secrets Manager form when plan supports it", () => { + // Free plan doesn't offer Secrets Manager + component.productTier = ProductTierType.Free; + component.formGroup.controls.productTier.setValue(ProductTierType.Free); + component.changedProduct(); + expect(component.planOffersSecretsManager).toBe(false); + + // Teams plan offers Secrets Manager + component.productTier = ProductTierType.Teams; + component.formGroup.controls.productTier.setValue(ProductTierType.Teams); + component.changedProduct(); + expect(component.planOffersSecretsManager).toBe(true); + expect(component.secretsManagerForm.disabled).toBe(false); + }); + + it("should update Secrets Manager subtotal when values change", () => { + component.productTier = ProductTierType.Teams; + component.changedProduct(); + + component.secretsManagerForm.patchValue({ + enabled: false, + }); + expect(component.secretsManagerSubtotal).toBe(0); + + component.secretsManagerForm.patchValue({ + enabled: true, + userSeats: 3, + additionalServiceAccounts: 10, + }); + + const smSubtotal = component.secretsManagerSubtotal; + expect(smSubtotal).toBeGreaterThan(0); + }); + }); + + describe("payment method and billing flow", () => { + beforeEach(async () => { + fixture.detectChanges(); + await fixture.whenStable(); + }); + + it("should update payment method during upgrade when required", async () => { + mockOrganization = { + id: "org-123", + name: "Test Org", + productTierType: ProductTierType.Free, + seats: 5, + hasPublicAndPrivateKeys: true, + } as Organization; + + organizationsSubject.next([mockOrganization]); + + mockOrganizationApiService.getBilling.mockResolvedValue({ + paymentSource: null, // No existing payment source + } as any); + + mockOrganizationApiService.getSubscription.mockResolvedValue({ + planType: PlanType.Free, + } as any); + + const upgradeFixture = TestBed.createComponent(OrganizationPlansComponent); + const upgradeComponent = upgradeFixture.componentInstance; + upgradeComponent.organizationId = "org-123"; + upgradeComponent.showFree = false; // Triggers upgradeRequiresPaymentMethod + + upgradeFixture.detectChanges(); + await upgradeFixture.whenStable(); + + upgradeComponent.productTier = ProductTierType.Teams; + upgradeComponent.plan = PlanType.TeamsAnnually; + upgradeComponent.formGroup.controls.additionalSeats.setValue(5); + + upgradeComponent.billingFormGroup.controls.billingAddress.patchValue({ + country: "US", + postalCode: "12345", + line1: "123 Street", + city: "City", + state: "CA", + }); + + upgradeComponent["enterPaymentMethodComponent"] = { + tokenize: jest.fn().mockResolvedValue({ + token: "new_token", + type: "card", + }), + } as any; + + mockOrganizationApiService.upgrade.mockResolvedValue(undefined); + + await upgradeComponent.submit(); + + expect(mockSubscriberBillingClient.updatePaymentMethod).toHaveBeenCalledWith( + { type: "organization", data: mockOrganization }, + { token: "new_token", type: "card" }, + { country: "US", postalCode: "12345" }, + ); + + expect(mockOrganizationApiService.upgrade).toHaveBeenCalled(); + }); + + it("should validate billing form for paid plans during creation", async () => { + component.formGroup.patchValue({ + name: "New Org", + billingEmail: "test@example.com", + productTier: ProductTierType.Teams, + plan: PlanType.TeamsAnnually, + additionalSeats: 5, + }); + + // Invalid billing form - explicitly mark as invalid since we removed validators from mock forms + component.billingFormGroup.controls.billingAddress.patchValue({ + country: "", + postalCode: "", + }); + + await component.submit(); + + expect(mockOrganizationApiService.create).not.toHaveBeenCalled(); + expect(component.billingFormGroup.invalid).toBe(true); + }); + + it("should not require billing validation for Free plan", async () => { + component.formGroup.patchValue({ + name: "Free Org", + billingEmail: "test@example.com", + productTier: ProductTierType.Free, + plan: PlanType.Free, + }); + + // Leave billing form empty + component.billingFormGroup.reset(); + + mockKeyService.makeOrgKey.mockResolvedValue([ + { encryptedString: "mock-key" }, + {} as any, + ] as any); + + mockEncryptService.encryptString.mockResolvedValue({ + encryptedString: "mock-collection", + } as any); + + mockKeyService.makeKeyPair.mockResolvedValue([ + "public-key", + { encryptedString: "private-key" }, + ] as any); + + mockOrganizationApiService.create.mockResolvedValue({ + id: "free-org-id", + } as any); + + await component.submit(); + + expect(mockOrganizationApiService.create).toHaveBeenCalled(); + }); + }); +}); diff --git a/apps/web/src/app/billing/organizations/organization-plans.component.ts b/apps/web/src/app/billing/organizations/organization-plans.component.ts index 3364ce2cbea..73fea30fa83 100644 --- a/apps/web/src/app/billing/organizations/organization-plans.component.ts +++ b/apps/web/src/app/billing/organizations/organization-plans.component.ts @@ -113,8 +113,6 @@ export class OrganizationPlansComponent implements OnInit, OnDestroy { // eslint-disable-next-line @angular-eslint/prefer-signals @Input() currentPlan: PlanResponse; - selectedFile: File; - // FIXME(https://bitwarden.atlassian.net/browse/CL-903): Migrate to Signals // eslint-disable-next-line @angular-eslint/prefer-signals @Input() @@ -675,9 +673,7 @@ export class OrganizationPlansComponent implements OnInit, OnDestroy { const collectionCt = collection.encryptedString; const orgKeys = await this.keyService.makeKeyPair(orgKey[1]); - orgId = this.selfHosted - ? await this.createSelfHosted(key, collectionCt, orgKeys) - : await this.createCloudHosted(key, collectionCt, orgKeys, orgKey[1], activeUserId); + orgId = await this.createCloudHosted(key, collectionCt, orgKeys, orgKey[1], activeUserId); this.toastService.showToast({ variant: "success", @@ -953,27 +949,6 @@ export class OrganizationPlansComponent implements OnInit, OnDestroy { } } - private async createSelfHosted(key: string, collectionCt: string, orgKeys: [string, EncString]) { - if (!this.selectedFile) { - throw new Error(this.i18nService.t("selectFile")); - } - - const fd = new FormData(); - fd.append("license", this.selectedFile); - fd.append("key", key); - fd.append("collectionName", collectionCt); - const response = await this.organizationApiService.createLicense(fd); - const orgId = response.id; - - await this.apiService.refreshIdentityToken(); - - // Org Keys live outside of the OrganizationLicense - add the keys to the org here - const request = new OrganizationKeysRequest(orgKeys[0], orgKeys[1].encryptedString); - await this.organizationApiService.updateKeys(orgId, request); - - return orgId; - } - private billingSubLabelText(): string { const selectedPlan = this.selectedPlan; const price = diff --git a/apps/web/src/app/core/core.module.ts b/apps/web/src/app/core/core.module.ts index b3afb8ca984..d270162f99d 100644 --- a/apps/web/src/app/core/core.module.ts +++ b/apps/web/src/app/core/core.module.ts @@ -126,6 +126,7 @@ import { SessionTimeoutSettingsComponentService, } from "@bitwarden/key-management-ui"; import { SerializedMemoryStorageService } from "@bitwarden/storage-core"; +import { UserCryptoManagementModule } from "@bitwarden/user-crypto-management"; import { DefaultSshImportPromptService, SshImportPromptService } from "@bitwarden/vault"; import { WebOrganizationInviteService } from "@bitwarden/web-vault/app/auth/core/services/organization-invite/web-organization-invite.service"; import { WebVaultPremiumUpgradePromptService } from "@bitwarden/web-vault/app/vault/services/web-premium-upgrade-prompt.service"; @@ -497,7 +498,7 @@ const safeProviders: SafeProvider[] = [ @NgModule({ declarations: [], - imports: [CommonModule, JslibServicesModule, GeneratorServicesModule], + imports: [CommonModule, JslibServicesModule, UserCryptoManagementModule, GeneratorServicesModule], // Do not register your dependency here! Add it to the typesafeProviders array using the helper function providers: safeProviders, }) diff --git a/apps/web/src/app/core/event.service.ts b/apps/web/src/app/core/event.service.ts index 47f4344ec36..006014b9fed 100644 --- a/apps/web/src/app/core/event.service.ts +++ b/apps/web/src/app/core/event.service.ts @@ -355,6 +355,13 @@ export class EventService { this.getShortId(ev.organizationUserId), ); break; + case EventType.OrganizationUser_AutomaticallyConfirmed: + msg = this.i18nService.t("automaticallyConfirmedUserId", this.formatOrgUserId(ev)); + humanReadableMsg = this.i18nService.t( + "automaticallyConfirmedUserId", + this.getShortId(ev.organizationUserId), + ); + break; // Org case EventType.Organization_Updated: msg = humanReadableMsg = this.i18nService.t("editedOrgSettings"); @@ -458,6 +465,18 @@ export class EventService { case EventType.Organization_ItemOrganization_Declined: msg = humanReadableMsg = this.i18nService.t("userDeclinedTransfer"); break; + case EventType.Organization_AutoConfirmEnabled_Admin: + msg = humanReadableMsg = this.i18nService.t("autoConfirmEnabledByAdmin"); + break; + case EventType.Organization_AutoConfirmDisabled_Admin: + msg = humanReadableMsg = this.i18nService.t("autoConfirmDisabledByAdmin"); + break; + case EventType.Organization_AutoConfirmEnabled_Portal: + msg = humanReadableMsg = this.i18nService.t("autoConfirmEnabledByPortal"); + break; + case EventType.Organization_AutoConfirmDisabled_Portal: + msg = humanReadableMsg = this.i18nService.t("autoConfirmDisabledByPortal"); + break; // Policies case EventType.Policy_Updated: { diff --git a/apps/web/src/app/dirt/reports/pages/exposed-passwords-report.component.html b/apps/web/src/app/dirt/reports/pages/exposed-passwords-report.component.html index 144396d6772..56316fcddee 100644 --- a/apps/web/src/app/dirt/reports/pages/exposed-passwords-report.component.html +++ b/apps/web/src/app/dirt/reports/pages/exposed-passwords-report.component.html @@ -43,16 +43,16 @@ > } } - + - + {{ "name" | i18n }} @if (!isAdminConsoleActive) { - + {{ "owner" | i18n }} } - + {{ "timesExposed" | i18n }} @@ -60,7 +60,7 @@ - + @if (!organization || canManageCipher(row)) { } @else { - {{ row.name }} + {{ row.name }} } @if (!organization && row.organizationId) { { expect(component).toBeTruthy(); }); - it('should get only ciphers with exposed passwords that the user has "Can Edit" access to', async () => { - const expectedIdOne: any = "cbea34a8-bde4-46ad-9d19-b05001228ab2"; - const expectedIdTwo = "cbea34a8-bde4-46ad-9d19-b05001228cd3"; - + it("should get ciphers with exposed passwords regardless of edit access", async () => { jest.spyOn(auditService, "passwordLeaked").mockReturnValue(Promise.resolve(1234)); jest.spyOn(component as any, "getAllCiphers").mockReturnValue(Promise.resolve(cipherData)); await component.setCiphers(); - expect(component.ciphers.length).toEqual(2); - expect(component.ciphers[0].id).toEqual(expectedIdOne); - expect(component.ciphers[0].edit).toEqual(true); - expect(component.ciphers[1].id).toEqual(expectedIdTwo); - expect(component.ciphers[1].edit).toEqual(true); + const cipherIds = component.ciphers.map((c) => c.id); + expect(cipherIds).toContain("cbea34a8-bde4-46ad-9d19-b05001228ab1"); + expect(cipherIds).toContain("cbea34a8-bde4-46ad-9d19-b05001228ab2"); + expect(cipherIds).toContain("cbea34a8-bde4-46ad-9d19-b05001228cd3"); + expect(component.ciphers.length).toEqual(3); }); it("should call fullSync method of syncService", () => { diff --git a/apps/web/src/app/dirt/reports/pages/exposed-passwords-report.component.ts b/apps/web/src/app/dirt/reports/pages/exposed-passwords-report.component.ts index 51bdde3eda8..e39ef811d66 100644 --- a/apps/web/src/app/dirt/reports/pages/exposed-passwords-report.component.ts +++ b/apps/web/src/app/dirt/reports/pages/exposed-passwords-report.component.ts @@ -64,14 +64,12 @@ export class ExposedPasswordsReportComponent extends CipherReportComponent imple this.filterStatus = [0]; allCiphers.forEach((ciph) => { - const { type, login, isDeleted, edit, viewPassword } = ciph; + const { type, login, isDeleted } = ciph; if ( type !== CipherType.Login || login.password == null || login.password === "" || - isDeleted || - (!this.organization && !edit) || - !viewPassword + isDeleted ) { return; } diff --git a/apps/web/src/app/dirt/reports/pages/inactive-two-factor-report.component.html b/apps/web/src/app/dirt/reports/pages/inactive-two-factor-report.component.html index 83c7e566619..b9512df8e3c 100644 --- a/apps/web/src/app/dirt/reports/pages/inactive-two-factor-report.component.html +++ b/apps/web/src/app/dirt/reports/pages/inactive-two-factor-report.component.html @@ -45,20 +45,20 @@ > } } - - @if (!isAdminConsoleActive) { - - - {{ "name" | i18n }} - {{ "owner" | i18n }} - - - } + + + + {{ "name" | i18n }} + @if (!isAdminConsoleActive) { + {{ "owner" | i18n }} + } + + - + @if (!organization || canManageCipher(row)) { {{ row.name }} } @else { - {{ row.name }} + {{ row.name }} } @if (!organization && row.organizationId) { {{ row.subTitle }} - - @if (!organization) { - - } - + @if (!isAdminConsoleActive) { + + @if (!organization) { + + } + + } @if (cipherDocs.has(row.id)) { diff --git a/apps/web/src/app/dirt/reports/pages/inactive-two-factor-report.component.spec.ts b/apps/web/src/app/dirt/reports/pages/inactive-two-factor-report.component.spec.ts index 12453ea3b88..07a772755f5 100644 --- a/apps/web/src/app/dirt/reports/pages/inactive-two-factor-report.component.spec.ts +++ b/apps/web/src/app/dirt/reports/pages/inactive-two-factor-report.component.spec.ts @@ -95,9 +95,7 @@ describe("InactiveTwoFactorReportComponent", () => { expect(component).toBeTruthy(); }); - it('should get only ciphers with domains in the 2fa directory that they have "Can Edit" access to', async () => { - const expectedIdOne: any = "cbea34a8-bde4-46ad-9d19-b05001228xy4"; - const expectedIdTwo: any = "cbea34a8-bde4-46ad-9d19-b05001227nm5"; + it("should get ciphers with domains in the 2fa directory regardless of edit access", async () => { component.services.set( "101domain.com", "https://help.101domain.com/account-management/account-security/enabling-disabling-two-factor-verification", @@ -110,11 +108,10 @@ describe("InactiveTwoFactorReportComponent", () => { jest.spyOn(component as any, "getAllCiphers").mockReturnValue(Promise.resolve(cipherData)); await component.setCiphers(); + const cipherIds = component.ciphers.map((c) => c.id); + expect(cipherIds).toContain("cbea34a8-bde4-46ad-9d19-b05001228xy4"); + expect(cipherIds).toContain("cbea34a8-bde4-46ad-9d19-b05001227nm5"); expect(component.ciphers.length).toEqual(2); - expect(component.ciphers[0].id).toEqual(expectedIdOne); - expect(component.ciphers[0].edit).toEqual(true); - expect(component.ciphers[1].id).toEqual(expectedIdTwo); - expect(component.ciphers[1].edit).toEqual(true); }); it("should call fullSync method of syncService", () => { @@ -197,7 +194,7 @@ describe("InactiveTwoFactorReportComponent", () => { expect(doc).toBe(""); }); - it("should return false if cipher does not have edit access and no organization", () => { + it("should return true for cipher without edit access", () => { component.organization = null; const cipher = createCipherView({ edit: false, @@ -206,11 +203,11 @@ describe("InactiveTwoFactorReportComponent", () => { }, }); const [doc, isInactive] = (component as any).isInactive2faCipher(cipher); - expect(isInactive).toBe(false); - expect(doc).toBe(""); + expect(isInactive).toBe(true); + expect(doc).toBe("https://example.com/2fa-doc"); }); - it("should return false if cipher does not have viewPassword", () => { + it("should return true for cipher without viewPassword", () => { const cipher = createCipherView({ viewPassword: false, login: { @@ -218,8 +215,8 @@ describe("InactiveTwoFactorReportComponent", () => { }, }); const [doc, isInactive] = (component as any).isInactive2faCipher(cipher); - expect(isInactive).toBe(false); - expect(doc).toBe(""); + expect(isInactive).toBe(true); + expect(doc).toBe("https://example.com/2fa-doc"); }); it("should check all uris and return true if any matches domain or host", () => { diff --git a/apps/web/src/app/dirt/reports/pages/inactive-two-factor-report.component.ts b/apps/web/src/app/dirt/reports/pages/inactive-two-factor-report.component.ts index 9d7de688f3e..cd892130518 100644 --- a/apps/web/src/app/dirt/reports/pages/inactive-two-factor-report.component.ts +++ b/apps/web/src/app/dirt/reports/pages/inactive-two-factor-report.component.ts @@ -92,14 +92,12 @@ export class InactiveTwoFactorReportComponent extends CipherReportComponent impl let docFor2fa: string = ""; let isInactive2faCipher: boolean = false; - const { type, login, isDeleted, edit, viewPassword } = cipher; + const { type, login, isDeleted } = cipher; if ( type !== CipherType.Login || (login.totp != null && login.totp !== "") || !login.hasUris || - isDeleted || - (!this.organization && !edit) || - !viewPassword + isDeleted ) { return [docFor2fa, isInactive2faCipher]; } diff --git a/apps/web/src/app/dirt/reports/pages/reused-passwords-report.component.html b/apps/web/src/app/dirt/reports/pages/reused-passwords-report.component.html index f08af8bda01..66bd11e7bc3 100644 --- a/apps/web/src/app/dirt/reports/pages/reused-passwords-report.component.html +++ b/apps/web/src/app/dirt/reports/pages/reused-passwords-report.component.html @@ -45,20 +45,20 @@ > } } - - @if (!isAdminConsoleActive) { - - - {{ "name" | i18n }} - {{ "owner" | i18n }} - {{ "timesReused" | i18n }} - - } + + + + {{ "name" | i18n }} + @if (!isAdminConsoleActive) { + {{ "owner" | i18n }} + } + {{ "timesReused" | i18n }} + - + @if (!organization || canManageCipher(row)) { {{ row.name }} } @else { - {{ row.name }} + {{ row.name }} } @if (!organization && row.organizationId) { {{ row.subTitle }} - - @if (!organization) { - - - } - + @if (!isAdminConsoleActive) { + + @if (!organization) { + + + } + + } {{ "reusedXTimes" | i18n: passwordUseMap.get(row.login.password) }} diff --git a/apps/web/src/app/dirt/reports/pages/reused-passwords-report.component.spec.ts b/apps/web/src/app/dirt/reports/pages/reused-passwords-report.component.spec.ts index 1b7006d0c68..8f08d06e27b 100644 --- a/apps/web/src/app/dirt/reports/pages/reused-passwords-report.component.spec.ts +++ b/apps/web/src/app/dirt/reports/pages/reused-passwords-report.component.spec.ts @@ -109,17 +109,15 @@ describe("ReusedPasswordsReportComponent", () => { expect(component).toBeTruthy(); }); - it('should get ciphers with reused passwords that the user has "Can Edit" access to', async () => { - const expectedIdOne: any = "cbea34a8-bde4-46ad-9d19-b05001228ab2"; - const expectedIdTwo = "cbea34a8-bde4-46ad-9d19-b05001228cd3"; + it("should get ciphers with reused passwords regardless of edit access", async () => { jest.spyOn(component as any, "getAllCiphers").mockReturnValue(Promise.resolve(cipherData)); await component.setCiphers(); - expect(component.ciphers.length).toEqual(2); - expect(component.ciphers[0].id).toEqual(expectedIdOne); - expect(component.ciphers[0].edit).toEqual(true); - expect(component.ciphers[1].id).toEqual(expectedIdTwo); - expect(component.ciphers[1].edit).toEqual(true); + const cipherIds = component.ciphers.map((c) => c.id); + expect(cipherIds).toContain("cbea34a8-bde4-46ad-9d19-b05001228ab1"); + expect(cipherIds).toContain("cbea34a8-bde4-46ad-9d19-b05001228ab2"); + expect(cipherIds).toContain("cbea34a8-bde4-46ad-9d19-b05001228cd3"); + expect(component.ciphers.length).toEqual(3); }); it("should call fullSync method of syncService", () => { diff --git a/apps/web/src/app/dirt/reports/pages/reused-passwords-report.component.ts b/apps/web/src/app/dirt/reports/pages/reused-passwords-report.component.ts index 0a81b19d4ff..7d24e61f276 100644 --- a/apps/web/src/app/dirt/reports/pages/reused-passwords-report.component.ts +++ b/apps/web/src/app/dirt/reports/pages/reused-passwords-report.component.ts @@ -71,14 +71,12 @@ export class ReusedPasswordsReportComponent extends CipherReportComponent implem this.filterStatus = [0]; ciphers.forEach((ciph) => { - const { type, login, isDeleted, edit, viewPassword } = ciph; + const { type, login, isDeleted } = ciph; if ( type !== CipherType.Login || login.password == null || login.password === "" || - isDeleted || - (!this.organization && !edit) || - !viewPassword + isDeleted ) { return; } diff --git a/apps/web/src/app/dirt/reports/pages/unsecured-websites-report.component.html b/apps/web/src/app/dirt/reports/pages/unsecured-websites-report.component.html index 810c1e384b0..553c3f2f04e 100644 --- a/apps/web/src/app/dirt/reports/pages/unsecured-websites-report.component.html +++ b/apps/web/src/app/dirt/reports/pages/unsecured-websites-report.component.html @@ -45,19 +45,19 @@ > } } - - @if (!isAdminConsoleActive) { - - - {{ "name" | i18n }} - {{ "owner" | i18n }} - - } + + + + {{ "name" | i18n }} + @if (!isAdminConsoleActive) { + {{ "owner" | i18n }} + } + - + @if (!organization || canManageCipher(row)) { {{ row.name }} } @else { - {{ row.name }} + {{ row.name }} } @if (!organization && row.organizationId) { {{ row.subTitle }} - - @if (!organization) { - - - } - + @if (!isAdminConsoleActive) { + + @if (!organization) { + + + } + + } } diff --git a/apps/web/src/app/dirt/reports/pages/unsecured-websites-report.component.spec.ts b/apps/web/src/app/dirt/reports/pages/unsecured-websites-report.component.spec.ts index 2107e0c8df7..f116faf114f 100644 --- a/apps/web/src/app/dirt/reports/pages/unsecured-websites-report.component.spec.ts +++ b/apps/web/src/app/dirt/reports/pages/unsecured-websites-report.component.spec.ts @@ -118,17 +118,14 @@ describe("UnsecuredWebsitesReportComponent", () => { expect(component).toBeTruthy(); }); - it('should get only unsecured ciphers that the user has "Can Edit" access to', async () => { - const expectedIdOne: any = "cbea34a8-bde4-46ad-9d19-b05001228ab2"; - const expectedIdTwo = "cbea34a8-bde4-46ad-9d19-b05001228cd3"; + it("should get unsecured ciphers regardless of edit access", async () => { jest.spyOn(component as any, "getAllCiphers").mockReturnValue(Promise.resolve(cipherData)); await component.setCiphers(); + const cipherIds = component.ciphers.map((c) => c.id); + expect(cipherIds).toContain("cbea34a8-bde4-46ad-9d19-b05001228ab2"); + expect(cipherIds).toContain("cbea34a8-bde4-46ad-9d19-b05001228cd3"); expect(component.ciphers.length).toEqual(2); - expect(component.ciphers[0].id).toEqual(expectedIdOne); - expect(component.ciphers[0].edit).toEqual(true); - expect(component.ciphers[1].id).toEqual(expectedIdTwo); - expect(component.ciphers[1].edit).toEqual(true); }); it("should call fullSync method of syncService", () => { diff --git a/apps/web/src/app/dirt/reports/pages/unsecured-websites-report.component.ts b/apps/web/src/app/dirt/reports/pages/unsecured-websites-report.component.ts index 4a2c0677574..8399395d273 100644 --- a/apps/web/src/app/dirt/reports/pages/unsecured-websites-report.component.ts +++ b/apps/web/src/app/dirt/reports/pages/unsecured-websites-report.component.ts @@ -71,12 +71,7 @@ export class UnsecuredWebsitesReportComponent extends CipherReportComponent impl * @param cipher Current cipher with unsecured uri */ private cipherContainsUnsecured(cipher: CipherView): boolean { - if ( - cipher.type !== CipherType.Login || - !cipher.login.hasUris || - cipher.isDeleted || - (!this.organization && !cipher.edit) - ) { + if (cipher.type !== CipherType.Login || !cipher.login.hasUris || cipher.isDeleted) { return false; } diff --git a/apps/web/src/app/dirt/reports/pages/weak-passwords-report.component.html b/apps/web/src/app/dirt/reports/pages/weak-passwords-report.component.html index 5a187427b5e..fd5b916e661 100644 --- a/apps/web/src/app/dirt/reports/pages/weak-passwords-report.component.html +++ b/apps/web/src/app/dirt/reports/pages/weak-passwords-report.component.html @@ -45,12 +45,12 @@ > } } - + - + {{ "name" | i18n }} @if (!isAdminConsoleActive) { - + {{ "owner" | i18n }} } @@ -62,7 +62,7 @@ - + @if (!organization || canManageCipher(row)) { {{ row.name }} } @else { - {{ row.name }} + {{ row.name }} } @if (!organization && row.organizationId) { { expect(component).toBeTruthy(); }); - it('should get only ciphers with weak passwords that the user has "Can Edit" access to', async () => { - const expectedIdOne: any = "cbea34a8-bde4-46ad-9d19-b05001228ab2"; - const expectedIdTwo = "cbea34a8-bde4-46ad-9d19-b05001228cd3"; - + it("should get ciphers with weak passwords regardless of edit access", async () => { jest.spyOn(passwordStrengthService, "getPasswordStrength").mockReturnValue({ password: "123", score: 0, @@ -125,11 +122,11 @@ describe("WeakPasswordsReportComponent", () => { jest.spyOn(component as any, "getAllCiphers").mockReturnValue(Promise.resolve(cipherData)); await component.setCiphers(); - expect(component.ciphers.length).toEqual(2); - expect(component.ciphers[0].id).toEqual(expectedIdOne); - expect(component.ciphers[0].edit).toEqual(true); - expect(component.ciphers[1].id).toEqual(expectedIdTwo); - expect(component.ciphers[1].edit).toEqual(true); + const cipherIds = component.ciphers.map((c) => c.id); + expect(cipherIds).toContain("cbea34a8-bde4-46ad-9d19-b05001228ab1"); + expect(cipherIds).toContain("cbea34a8-bde4-46ad-9d19-b05001228ab2"); + expect(cipherIds).toContain("cbea34a8-bde4-46ad-9d19-b05001228cd3"); + expect(component.ciphers.length).toEqual(3); }); it("should call fullSync method of syncService", () => { diff --git a/apps/web/src/app/dirt/reports/pages/weak-passwords-report.component.ts b/apps/web/src/app/dirt/reports/pages/weak-passwords-report.component.ts index bb5400346fd..6cde01f2d92 100644 --- a/apps/web/src/app/dirt/reports/pages/weak-passwords-report.component.ts +++ b/apps/web/src/app/dirt/reports/pages/weak-passwords-report.component.ts @@ -103,15 +103,8 @@ export class WeakPasswordsReportComponent extends CipherReportComponent implemen } protected determineWeakPasswordScore(ciph: CipherView): ReportResult | null { - const { type, login, isDeleted, edit, viewPassword } = ciph; - if ( - type !== CipherType.Login || - login.password == null || - login.password === "" || - isDeleted || - (!this.organization && !edit) || - !viewPassword - ) { + const { type, login, isDeleted } = ciph; + if (type !== CipherType.Login || login.password == null || login.password === "" || isDeleted) { return; } diff --git a/apps/web/src/app/dirt/reports/reports-layout.component.html b/apps/web/src/app/dirt/reports/reports-layout.component.html index 0cb5d304a34..c290fc88335 100644 --- a/apps/web/src/app/dirt/reports/reports-layout.component.html +++ b/apps/web/src/app/dirt/reports/reports-layout.component.html @@ -1,11 +1,25 @@ -
    -
    - @if (!homepage) { - - {{ "backToReports" | i18n }} - - } +@if (!homepage) { + + +} + + + -
    + diff --git a/apps/web/src/app/dirt/reports/reports-layout.component.ts b/apps/web/src/app/dirt/reports/reports-layout.component.ts index a6d84ccb037..136b70c81e4 100644 --- a/apps/web/src/app/dirt/reports/reports-layout.component.ts +++ b/apps/web/src/app/dirt/reports/reports-layout.component.ts @@ -1,4 +1,14 @@ -import { Component } from "@angular/core"; +import { Overlay, OverlayRef } from "@angular/cdk/overlay"; +import { TemplatePortal } from "@angular/cdk/portal"; +import { + AfterViewInit, + Component, + inject, + OnDestroy, + TemplateRef, + viewChild, + ViewContainerRef, +} from "@angular/core"; import { takeUntilDestroyed } from "@angular/core/rxjs-interop"; import { NavigationEnd, Router } from "@angular/router"; import { filter } from "rxjs/operators"; @@ -10,20 +20,65 @@ import { filter } from "rxjs/operators"; templateUrl: "reports-layout.component.html", standalone: false, }) -export class ReportsLayoutComponent { +export class ReportsLayoutComponent implements AfterViewInit, OnDestroy { homepage = true; - constructor(router: Router) { - const reportsHomeRoute = "/reports"; + private readonly backButtonTemplate = + viewChild.required>("backButtonTemplate"); - this.homepage = router.url === reportsHomeRoute; - router.events + private overlayRef: OverlayRef | null = null; + private overlay = inject(Overlay); + private viewContainerRef = inject(ViewContainerRef); + private router = inject(Router); + + constructor() { + this.router.events .pipe( takeUntilDestroyed(), filter((event) => event instanceof NavigationEnd), ) - .subscribe((event) => { - this.homepage = (event as NavigationEnd).url == reportsHomeRoute; + .subscribe(() => this.updateOverlay()); + } + + ngAfterViewInit(): void { + this.updateOverlay(); + } + + ngOnDestroy(): void { + this.overlayRef?.dispose(); + } + + returnFocusToPage(event: Event): void { + if ((event as KeyboardEvent).shiftKey) { + return; // Allow natural Shift+Tab behavior + } + event.preventDefault(); + const firstFocusable = document.querySelector( + "[cdktrapfocus] a:not([tabindex='-1'])", + ) as HTMLElement; + firstFocusable?.focus(); + } + + focusOverlayButton(event: Event): void { + if ((event as KeyboardEvent).shiftKey) { + return; // Allow natural Shift+Tab behavior + } + event.preventDefault(); + const button = this.overlayRef?.overlayElement?.querySelector("a") as HTMLElement; + button?.focus(); + } + + private updateOverlay(): void { + if (this.router.url === "/reports") { + this.homepage = true; + this.overlayRef?.dispose(); + this.overlayRef = null; + } else if (!this.overlayRef) { + this.homepage = false; + this.overlayRef = this.overlay.create({ + positionStrategy: this.overlay.position().global().bottom("20px").right("32px"), }); + this.overlayRef.attach(new TemplatePortal(this.backButtonTemplate(), this.viewContainerRef)); + } } } diff --git a/apps/web/src/app/dirt/reports/reports.module.ts b/apps/web/src/app/dirt/reports/reports.module.ts index 4fc152917f4..c4bd9fef809 100644 --- a/apps/web/src/app/dirt/reports/reports.module.ts +++ b/apps/web/src/app/dirt/reports/reports.module.ts @@ -1,3 +1,4 @@ +import { OverlayModule } from "@angular/cdk/overlay"; import { CommonModule } from "@angular/common"; import { NgModule } from "@angular/core"; @@ -29,6 +30,7 @@ import { ReportsSharedModule } from "./shared"; @NgModule({ imports: [ CommonModule, + OverlayModule, SharedModule, ReportsSharedModule, ReportsRoutingModule, diff --git a/apps/web/src/app/shared/components/account-fingerprint/account-fingerprint.component.html b/apps/web/src/app/key-management/account-fingerprint/account-fingerprint.component.html similarity index 100% rename from apps/web/src/app/shared/components/account-fingerprint/account-fingerprint.component.html rename to apps/web/src/app/key-management/account-fingerprint/account-fingerprint.component.html diff --git a/apps/web/src/app/shared/components/account-fingerprint/account-fingerprint.component.ts b/apps/web/src/app/key-management/account-fingerprint/account-fingerprint.component.ts similarity index 96% rename from apps/web/src/app/shared/components/account-fingerprint/account-fingerprint.component.ts rename to apps/web/src/app/key-management/account-fingerprint/account-fingerprint.component.ts index eb84868dca1..ca9042e802e 100644 --- a/apps/web/src/app/shared/components/account-fingerprint/account-fingerprint.component.ts +++ b/apps/web/src/app/key-management/account-fingerprint/account-fingerprint.component.ts @@ -4,7 +4,7 @@ import { Component, Input, OnInit } from "@angular/core"; import { KeyService } from "@bitwarden/key-management"; -import { SharedModule } from "../../shared.module"; +import { SharedModule } from "../../shared/shared.module"; // FIXME(https://bitwarden.atlassian.net/browse/CL-764): Migrate to OnPush // eslint-disable-next-line @angular-eslint/prefer-on-push-component-change-detection diff --git a/apps/web/src/app/key-management/key-rotation/user-key-rotation.service.spec.ts b/apps/web/src/app/key-management/key-rotation/user-key-rotation.service.spec.ts index a2330025c92..fec972c82f2 100644 --- a/apps/web/src/app/key-management/key-rotation/user-key-rotation.service.spec.ts +++ b/apps/web/src/app/key-management/key-rotation/user-key-rotation.service.spec.ts @@ -57,6 +57,7 @@ import { KeyRotationTrustInfoComponent, } from "@bitwarden/key-management-ui"; import { BitwardenClient, PureCrypto } from "@bitwarden/sdk-internal"; +import { UserKeyRotationServiceAbstraction } from "@bitwarden/user-crypto-management"; import { OrganizationUserResetPasswordService } from "../../admin-console/organizations/members/services/organization-user-reset-password/organization-user-reset-password.service"; import { WebauthnLoginAdminService } from "../../auth"; @@ -287,6 +288,7 @@ describe("KeyRotationService", () => { let mockSdkClientFactory: MockProxy; let mockSecurityStateService: MockProxy; let mockMasterPasswordService: MockProxy; + let mockSdkUserKeyRotationService: MockProxy; const mockUser = { id: "mockUserId" as UserId, @@ -348,6 +350,7 @@ describe("KeyRotationService", () => { mockDialogService = mock(); mockCryptoFunctionService = mock(); mockKdfConfigService = mock(); + mockSdkUserKeyRotationService = mock(); mockSdkClientFactory = mock(); mockSdkClientFactory.createSdkClient.mockResolvedValue({ crypto: () => { @@ -358,6 +361,7 @@ describe("KeyRotationService", () => { } as any; }, } as BitwardenClient); + mockSecurityStateService = mock(); mockMasterPasswordService = mock(); @@ -384,6 +388,7 @@ describe("KeyRotationService", () => { mockSdkClientFactory, mockSecurityStateService, mockMasterPasswordService, + mockSdkUserKeyRotationService, ); }); @@ -509,7 +514,12 @@ describe("KeyRotationService", () => { ); mockKeyService.userSigningKey$.mockReturnValue(new BehaviorSubject(null)); mockSecurityStateService.accountSecurityState$.mockReturnValue(new BehaviorSubject(null)); - mockConfigService.getFeatureFlag.mockResolvedValue(true); + mockConfigService.getFeatureFlag.mockImplementation(async (flag: FeatureFlag) => { + if (flag === FeatureFlag.EnrollAeadOnKeyRotation) { + return true; + } + return false; + }); const spy = jest.spyOn(keyRotationService, "getRotatedAccountKeysFlagged").mockResolvedValue({ userKey: TEST_VECTOR_USER_KEY_V2, diff --git a/apps/web/src/app/key-management/key-rotation/user-key-rotation.service.ts b/apps/web/src/app/key-management/key-rotation/user-key-rotation.service.ts index 68253a4a35d..26dcacd8f11 100644 --- a/apps/web/src/app/key-management/key-rotation/user-key-rotation.service.ts +++ b/apps/web/src/app/key-management/key-rotation/user-key-rotation.service.ts @@ -39,6 +39,7 @@ import { KeyRotationTrustInfoComponent, } from "@bitwarden/key-management-ui"; import { PureCrypto, TokenProvider } from "@bitwarden/sdk-internal"; +import { UserKeyRotationServiceAbstraction } from "@bitwarden/user-crypto-management"; import { OrganizationUserResetPasswordService } from "../../admin-console/organizations/members/services/organization-user-reset-password/organization-user-reset-password.service"; import { WebauthnLoginAdminService } from "../../auth/core"; @@ -101,6 +102,7 @@ export class UserKeyRotationService { private sdkClientFactory: SdkClientFactory, private securityStateService: SecurityStateService, private masterPasswordService: MasterPasswordServiceAbstraction, + private sdkUserKeyRotationService: UserKeyRotationServiceAbstraction, ) {} /** @@ -116,6 +118,28 @@ export class UserKeyRotationService { user: Account, newMasterPasswordHint?: string, ): Promise { + const useSdkKeyRotation = await this.configService.getFeatureFlag(FeatureFlag.SdkKeyRotation); + if (useSdkKeyRotation) { + this.logService.info( + "[UserKey Rotation] Using SDK-based key rotation service from user-crypto-management", + ); + await this.sdkUserKeyRotationService.changePasswordAndRotateUserKey( + currentMasterPassword, + newMasterPassword, + newMasterPasswordHint, + asUuid(user.id), + ); + this.toastService.showToast({ + variant: "success", + title: this.i18nService.t("rotationCompletedTitle"), + message: this.i18nService.t("rotationCompletedDesc"), + timeout: 15000, + }); + + await this.logoutService.logout(user.id); + return; + } + // Key-rotation uses the SDK, so we need to ensure that the SDK is loaded / the WASM initialized. await SdkLoadService.Ready; diff --git a/apps/web/src/app/layouts/product-switcher/navigation-switcher/navigation-switcher.component.html b/apps/web/src/app/layouts/product-switcher/navigation-switcher/navigation-switcher.component.html index 9c8f2125614..9b95737df32 100644 --- a/apps/web/src/app/layouts/product-switcher/navigation-switcher/navigation-switcher.component.html +++ b/apps/web/src/app/layouts/product-switcher/navigation-switcher/navigation-switcher.component.html @@ -1,67 +1,66 @@ -
    - @let accessibleProducts = accessibleProducts$ | async; - @if (accessibleProducts && accessibleProducts.length > 1) { - - - - } +@let accessibleProducts = accessibleProducts$ | async; +@if (accessibleProducts && accessibleProducts.length > 1) { + + + +} - @if (shouldShowPremiumUpgradeButton$ | async) { - - } +@if (shouldShowPremiumUpgradeButton$ | async) { + +} - @let moreProducts = moreProducts$ | async; - @if (moreProducts && moreProducts.length > 0) { -
    - {{ "moreFromBitwarden" | i18n }} - - + +
    + + +} diff --git a/apps/web/src/app/layouts/product-switcher/navigation-switcher/navigation-switcher.component.spec.ts b/apps/web/src/app/layouts/product-switcher/navigation-switcher/navigation-switcher.component.spec.ts index 9a6de3ad9af..95676759147 100644 --- a/apps/web/src/app/layouts/product-switcher/navigation-switcher/navigation-switcher.component.spec.ts +++ b/apps/web/src/app/layouts/product-switcher/navigation-switcher/navigation-switcher.component.spec.ts @@ -8,7 +8,7 @@ import { BehaviorSubject } from "rxjs"; import { I18nPipe } from "@bitwarden/angular/platform/pipes/i18n.pipe"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { FakeGlobalStateProvider } from "@bitwarden/common/spec"; -import { IconButtonModule, NavigationModule } from "@bitwarden/components"; +import { IconButtonModule, NavigationModule, SideNavService } from "@bitwarden/components"; // FIXME: remove `src` and fix import // eslint-disable-next-line no-restricted-imports import { NavItemComponent } from "@bitwarden/components/src/navigation/nav-item.component"; @@ -86,6 +86,9 @@ describe("NavigationProductSwitcherComponent", () => { beforeEach(() => { fixture = TestBed.createComponent(NavigationProductSwitcherComponent); + // SideNavService.open starts false (managed by LayoutComponent's ResizeObserver in a real + // app). Set it to true so NavItemComponent renders text labels (used in text-content checks). + TestBed.inject(SideNavService).open.set(true); fixture.detectChanges(); }); diff --git a/apps/web/src/app/layouts/product-switcher/navigation-switcher/navigation-switcher.stories.ts b/apps/web/src/app/layouts/product-switcher/navigation-switcher/navigation-switcher.stories.ts index ba36063fb7b..990b1f63267 100644 --- a/apps/web/src/app/layouts/product-switcher/navigation-switcher/navigation-switcher.stories.ts +++ b/apps/web/src/app/layouts/product-switcher/navigation-switcher/navigation-switcher.stories.ts @@ -1,8 +1,10 @@ import { Component, Directive, importProvidersFrom, Input } from "@angular/core"; +import { provideNoopAnimations } from "@angular/platform-browser/animations"; import { RouterModule } from "@angular/router"; import { applicationConfig, Meta, moduleMetadata, StoryObj } from "@storybook/angular"; import { BehaviorSubject, Observable, of } from "rxjs"; +import { PasswordManagerLogo } from "@bitwarden/assets/svg"; import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction"; import { PolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction"; import { ProviderService } from "@bitwarden/common/admin-console/abstractions/provider.service"; @@ -17,13 +19,13 @@ import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/pl import { SyncService } from "@bitwarden/common/platform/sync"; import { UserId } from "@bitwarden/common/types/guid"; import { + I18nMockService, LayoutComponent, NavigationModule, StorybookGlobalStateProvider, } from "@bitwarden/components"; -// FIXME: remove `src` and fix import // eslint-disable-next-line no-restricted-imports -import { I18nMockService } from "@bitwarden/components/src/utils/i18n-mock.service"; +import { positionFixedWrapperDecorator } from "@bitwarden/components/src/stories/storybook-decorators"; import { GlobalStateProvider } from "@bitwarden/state"; import { I18nPipe } from "@bitwarden/ui-common"; @@ -105,19 +107,10 @@ class MockBillingAccountProfileStateService implements Partial { getFeatureFlag$(key: Flag): Observable> { - return of(false); + return of(false as FeatureFlagValueType); } } -// FIXME(https://bitwarden.atlassian.net/browse/CL-764): Migrate to OnPush -// eslint-disable-next-line @angular-eslint/prefer-on-push-component-change-detection -@Component({ - selector: "story-layout", - template: ``, - standalone: false, -}) -class StoryLayoutComponent {} - // FIXME(https://bitwarden.atlassian.net/browse/CL-764): Migrate to OnPush // eslint-disable-next-line @angular-eslint/prefer-on-push-component-change-detection @Component({ @@ -132,17 +125,23 @@ const translations: Record = { secureYourInfrastructure: "Secure your infrastructure", protectYourFamilyOrBusiness: "Protect your family or business", skipToContent: "Skip to content", + toggleSideNavigation: "Toggle side navigation", + resizeSideNavigation: "Resize side navigation", + submenu: "submenu", + toggleCollapse: "toggle collapse", + close: "Close", + loading: "Loading", }; export default { title: "Web/Navigation Product Switcher", decorators: [ + positionFixedWrapperDecorator(), moduleMetadata({ declarations: [ NavigationProductSwitcherComponent, MockOrganizationService, MockProviderService, - StoryLayoutComponent, StoryContentComponent, ], imports: [NavigationModule, RouterModule, LayoutComponent, I18nPipe], @@ -174,19 +173,11 @@ export default { }), applicationConfig({ providers: [ + provideNoopAnimations(), importProvidersFrom( - RouterModule.forRoot([ - { - path: "", - component: StoryLayoutComponent, - children: [ - { - path: "**", - component: StoryContentComponent, - }, - ], - }, - ]), + RouterModule.forRoot([{ path: "**", component: StoryContentComponent }], { + useHash: true, + }), ), { provide: GlobalStateProvider, @@ -203,12 +194,47 @@ type Story = StoryObj< const Template: Story = { render: (args) => ({ - props: args, + props: { ...args, logo: PasswordManagerLogo }, template: ` - -
    - -
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + `, }), }; diff --git a/apps/web/src/app/layouts/product-switcher/product-switcher-content.component.html b/apps/web/src/app/layouts/product-switcher/product-switcher-content.component.html index f2154ec74a3..290e07c932a 100644 --- a/apps/web/src/app/layouts/product-switcher/product-switcher-content.component.html +++ b/apps/web/src/app/layouts/product-switcher/product-switcher-content.component.html @@ -19,6 +19,9 @@ " class="tw-group/product-link tw-flex tw-h-24 tw-w-28 tw-flex-col tw-items-center tw-justify-center tw-rounded tw-p-1 tw-text-primary-600 tw-outline-none hover:tw-bg-background-alt hover:tw-text-primary-700 hover:tw-no-underline focus-visible:!tw-ring-2 focus-visible:!tw-ring-primary-700" ariaCurrentWhenActive="page" + [state]="{ + focusAfterNav: 'body', + }" > { getFeatureFlag$(key: Flag): Observable> { - return of(false); + return of(false as FeatureFlagValueType); } } diff --git a/apps/web/src/app/layouts/product-switcher/shared/product-switcher.service.ts b/apps/web/src/app/layouts/product-switcher/shared/product-switcher.service.ts index 31fcf9ffe6d..fae8944a10f 100644 --- a/apps/web/src/app/layouts/product-switcher/shared/product-switcher.service.ts +++ b/apps/web/src/app/layouts/product-switcher/shared/product-switcher.service.ts @@ -159,16 +159,14 @@ export class ProductSwitcherService { this.userHasSingleOrgPolicy$, this.route.paramMap, this.triggerProductUpdate$, - this.configService.getFeatureFlag$(FeatureFlag.SM1719_RemoveSecretsManagerAds), ]).pipe( map( - ([orgs, providers, userHasSingleOrgPolicy, paramMap, , removeSecretsManagerAdsFlag]: [ + ([orgs, providers, userHasSingleOrgPolicy, paramMap]: [ Organization[], Provider[], boolean, ParamMap, void, - boolean, ]) => { // Sort orgs by name to match the order within the sidebar orgs.sort((a, b) => a.name.localeCompare(b.name)); @@ -215,13 +213,11 @@ export class ProductSwitcherService { }; // Check if SM ads should be disabled for any organization - // SM ads are only disabled if the feature flag is enabled AND - // the user is a regular User (not Admin or Owner) in an organization that has useDisableSMAdsForUsers enabled - const shouldDisableSMAds = - removeSecretsManagerAdsFlag && - orgs.some( - (org) => org.useDisableSMAdsForUsers === true && org.type === OrganizationUserType.User, - ); + // SM ads are disabled if the user is a regular User (not Admin or Owner) + // in an organization that has useDisableSMAdsForUsers enabled + const shouldDisableSMAds = orgs.some( + (org) => org.useDisableSMAdsForUsers === true && org.type === OrganizationUserType.User, + ); const products = { pm: { diff --git a/apps/web/src/app/layouts/user-layout.component.html b/apps/web/src/app/layouts/user-layout.component.html index 10f569e2558..57b8cf047c4 100644 --- a/apps/web/src/app/layouts/user-layout.component.html +++ b/apps/web/src/app/layouts/user-layout.component.html @@ -3,7 +3,9 @@ - + @if (sendEnabled$ | async) { + + } diff --git a/apps/web/src/app/layouts/user-layout.component.ts b/apps/web/src/app/layouts/user-layout.component.ts index 33bce661c65..6af7b0639e5 100644 --- a/apps/web/src/app/layouts/user-layout.component.ts +++ b/apps/web/src/app/layouts/user-layout.component.ts @@ -4,12 +4,13 @@ import { CommonModule } from "@angular/common"; import { Component, OnInit, Signal } from "@angular/core"; import { toSignal } from "@angular/core/rxjs-interop"; import { RouterModule } from "@angular/router"; -import { Observable, switchMap } from "rxjs"; +import { map, Observable, switchMap } from "rxjs"; import { JslibModule } from "@bitwarden/angular/jslib.module"; import { PasswordManagerLogo } from "@bitwarden/assets/svg"; import { canAccessEmergencyAccess } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction"; import { PolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction"; +import { PolicyType } from "@bitwarden/common/admin-console/enums"; import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; import { getUserId } from "@bitwarden/common/auth/services/account.service"; import { BillingAccountProfileStateService } from "@bitwarden/common/billing/abstractions/account/billing-account-profile-state.service"; @@ -42,6 +43,11 @@ export class UserLayoutComponent implements OnInit { protected hasFamilySponsorshipAvailable$: Observable; protected showSponsoredFamilies$: Observable; protected showSubscription$: Observable; + protected readonly sendEnabled$: Observable = this.accountService.activeAccount$.pipe( + getUserId, + switchMap((userId) => this.policyService.policyAppliesToUser$(PolicyType.DisableSend, userId)), + map((isDisabled) => !isDisabled), + ); protected consolidatedSessionTimeoutComponent$: Observable; constructor( diff --git a/apps/web/src/app/layouts/web-side-nav.component.html b/apps/web/src/app/layouts/web-side-nav.component.html index adb526bd593..081afc355a6 100644 --- a/apps/web/src/app/layouts/web-side-nav.component.html +++ b/apps/web/src/app/layouts/web-side-nav.component.html @@ -1,8 +1,12 @@ - + + + + + diff --git a/apps/web/src/app/oss-routing.module.ts b/apps/web/src/app/oss-routing.module.ts index 932d0b8119b..a5fe3f5d627 100644 --- a/apps/web/src/app/oss-routing.module.ts +++ b/apps/web/src/app/oss-routing.module.ts @@ -1,5 +1,6 @@ import { NgModule } from "@angular/core"; import { Route, RouterModule, Routes } from "@angular/router"; +import { map } from "rxjs"; import { organizationPolicyGuard } from "@bitwarden/angular/admin-console/guards"; import { AuthenticationTimeoutComponent } from "@bitwarden/angular/auth/components/authentication-timeout.component"; @@ -50,6 +51,7 @@ import { NewDeviceVerificationComponent, } from "@bitwarden/auth/angular"; import { canAccessEmergencyAccess } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction"; +import { PolicyType } from "@bitwarden/common/admin-console/enums"; import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum"; import { AnonLayoutWrapperComponent, AnonLayoutWrapperData } from "@bitwarden/components"; import { LockComponent, RemovePasswordComponent } from "@bitwarden/key-management-ui"; @@ -641,6 +643,13 @@ const routes: Routes = [ path: "sends", component: SendComponent, data: { titleId: "send" } satisfies RouteDataProperties, + canActivate: [ + organizationPolicyGuard((userId, _configService, policyService) => + policyService + .policyAppliesToUser$(PolicyType.DisableSend, userId) + .pipe(map((policyApplies) => !policyApplies)), + ), + ], }, { path: "sm-landing", diff --git a/apps/web/src/app/tools/send/new-send/new-send-dropdown.component.html b/apps/web/src/app/tools/send/new-send/new-send-dropdown.component.html index d1ee9d29ebd..2e681cae12d 100644 --- a/apps/web/src/app/tools/send/new-send/new-send-dropdown.component.html +++ b/apps/web/src/app/tools/send/new-send/new-send-dropdown.component.html @@ -1,17 +1,19 @@ - - + + diff --git a/apps/web/src/app/tools/send/new-send/new-send-dropdown.component.ts b/apps/web/src/app/tools/send/new-send/new-send-dropdown.component.ts index 68c8c188d31..b49f615fc74 100644 --- a/apps/web/src/app/tools/send/new-send/new-send-dropdown.component.ts +++ b/apps/web/src/app/tools/send/new-send/new-send-dropdown.component.ts @@ -1,4 +1,3 @@ -import { CommonModule } from "@angular/common"; import { Component, Input } from "@angular/core"; import { firstValueFrom, Observable, of, switchMap, lastValueFrom } from "rxjs"; @@ -9,7 +8,7 @@ import { BillingAccountProfileStateService } from "@bitwarden/common/billing/abs import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum"; import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; import { SendType } from "@bitwarden/common/tools/send/types/send-type"; -import { ButtonModule, DialogService, MenuModule } from "@bitwarden/components"; +import { ButtonModule, DialogService, IconComponent, MenuModule } from "@bitwarden/components"; import { DefaultSendFormConfigService, SendAddEditDialogComponent, @@ -23,7 +22,7 @@ import { SendSuccessDrawerDialogComponent } from "../shared"; @Component({ selector: "tools-new-send-dropdown", templateUrl: "new-send-dropdown.component.html", - imports: [JslibModule, CommonModule, ButtonModule, MenuModule, PremiumBadgeComponent], + imports: [JslibModule, ButtonModule, MenuModule, PremiumBadgeComponent, IconComponent], providers: [DefaultSendFormConfigService], }) /** diff --git a/apps/web/src/app/tools/send/send-access/send-access-email.component.html b/apps/web/src/app/tools/send/send-access/send-access-email.component.html index ee5a03670bb..03af33ce911 100644 --- a/apps/web/src/app/tools/send/send-access/send-access-email.component.html +++ b/apps/web/src/app/tools/send/send-access/send-access-email.component.html @@ -20,16 +20,19 @@ {{ "verificationCode" | i18n }} -
    - -
    + + } diff --git a/apps/web/src/app/tools/send/send-access/send-access-email.component.ts b/apps/web/src/app/tools/send/send-access/send-access-email.component.ts index b1374cd6c66..0915a47e4ad 100644 --- a/apps/web/src/app/tools/send/send-access/send-access-email.component.ts +++ b/apps/web/src/app/tools/send/send-access/send-access-email.component.ts @@ -1,6 +1,14 @@ // FIXME: Update this file to be type safe and remove this and next line // @ts-strict-ignore -import { ChangeDetectionStrategy, Component, input, OnDestroy, OnInit } from "@angular/core"; +import { + ChangeDetectionStrategy, + Component, + effect, + input, + OnDestroy, + OnInit, + output, +} from "@angular/core"; import { FormControl, FormGroup, Validators } from "@angular/forms"; import { SharedModule } from "../../../shared"; @@ -18,18 +26,45 @@ export class SendAccessEmailComponent implements OnInit, OnDestroy { protected otp: FormControl; readonly loading = input.required(); + readonly backToEmail = output(); constructor() {} ngOnInit() { this.email = new FormControl("", Validators.required); - this.otp = new FormControl("", Validators.required); + this.otp = new FormControl(""); this.formGroup().addControl("email", this.email); this.formGroup().addControl("otp", this.otp); - } + // Update validators when enterOtp changes + effect(() => { + const isOtpMode = this.enterOtp(); + if (isOtpMode) { + // In OTP mode: email is not required (already entered), otp is required + this.email.clearValidators(); + this.otp.setValidators([Validators.required]); + } else { + // In email mode: email is required, otp is not required + this.email.setValidators([Validators.required]); + this.otp.clearValidators(); + } + this.email.updateValueAndValidity(); + this.otp.updateValueAndValidity(); + }); + } ngOnDestroy() { this.formGroup().removeControl("email"); this.formGroup().removeControl("otp"); } + + onBackClick() { + this.backToEmail.emit(); + if (this.otp) { + this.otp.clearValidators(); + this.otp.setValue(""); + this.otp.setErrors(null); + this.otp.markAsUntouched(); + this.otp.markAsPristine(); + } + } } diff --git a/apps/web/src/app/tools/send/send-access/send-access-text.component.html b/apps/web/src/app/tools/send/send-access/send-access-text.component.html index ca772251146..746dd5f0567 100644 --- a/apps/web/src/app/tools/send/send-access/send-access-text.component.html +++ b/apps/web/src/app/tools/send/send-access/send-access-text.component.html @@ -1,26 +1,23 @@ -{{ "sendHiddenByDefault" | i18n }} +
    + @if (send.text.hidden) { + + } +
    -
    -
    -
    diff --git a/apps/web/src/app/tools/send/send-access/send-access-text.component.ts b/apps/web/src/app/tools/send/send-access/send-access-text.component.ts index 794cfbc9678..8a947eafb69 100644 --- a/apps/web/src/app/tools/send/send-access/send-access-text.component.ts +++ b/apps/web/src/app/tools/send/send-access/send-access-text.component.ts @@ -6,7 +6,7 @@ import { FormBuilder } from "@angular/forms"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; import { SendAccessView } from "@bitwarden/common/tools/send/models/view/send-access.view"; -import { ToastService } from "@bitwarden/components"; +import { IconModule, ToastService } from "@bitwarden/components"; import { SharedModule } from "../../../shared"; @@ -15,7 +15,7 @@ import { SharedModule } from "../../../shared"; @Component({ selector: "app-send-access-text", templateUrl: "send-access-text.component.html", - imports: [SharedModule], + imports: [SharedModule, IconModule], }) export class SendAccessTextComponent { private _send: SendAccessView = null; diff --git a/apps/web/src/app/tools/send/send-access/send-auth.component.html b/apps/web/src/app/tools/send/send-access/send-auth.component.html index c3e90cea4ea..8f9b05d7d13 100644 --- a/apps/web/src/app/tools/send/send-access/send-auth.component.html +++ b/apps/web/src/app/tools/send/send-access/send-auth.component.html @@ -1,13 +1,3 @@ -@if (loading()) { -
    - - {{ "loading" | i18n }} -
    -} @if (error()) {
    @@ -31,6 +21,7 @@ [formGroup]="sendAccessForm" [enterOtp]="enterOtp()" [loading]="loading()" + (backToEmail)="onBackToEmail()" > } } diff --git a/apps/web/src/app/tools/send/send-access/send-auth.component.ts b/apps/web/src/app/tools/send/send-access/send-auth.component.ts index 8c630ce5315..5582d6d8dcc 100644 --- a/apps/web/src/app/tools/send/send-access/send-auth.component.ts +++ b/apps/web/src/app/tools/send/send-access/send-auth.component.ts @@ -52,6 +52,7 @@ export class SendAuthComponent implements OnInit { authType = AuthType; private expiredAuthAttempts = 0; + private otpSubmitted = false; readonly loading = signal(false); readonly error = signal(false); @@ -80,13 +81,22 @@ export class SendAuthComponent implements OnInit { this.loading.set(true); this.unavailable.set(false); this.error.set(false); - const sendEmailOtp = await this.configService.getFeatureFlag(FeatureFlag.SendEmailOTP); - if (sendEmailOtp) { - await this.attemptV2Access(); - } else { - await this.attemptV1Access(); + try { + const sendEmailOtp = await this.configService.getFeatureFlag(FeatureFlag.SendEmailOTP); + if (sendEmailOtp) { + await this.attemptV2Access(); + } else { + await this.attemptV1Access(); + } + } finally { + this.loading.set(false); } - this.loading.set(false); + } + + onBackToEmail() { + this.enterOtp.set(false); + this.otpSubmitted = false; + this.updatePageTitle(); } private async attemptV1Access() { @@ -104,7 +114,27 @@ export class SendAuthComponent implements OnInit { } catch (e) { if (e instanceof ErrorResponse) { if (e.statusCode === 401) { + if (this.sendAuthType() === AuthType.Password) { + // Password was already required, so this is an invalid password error + const passwordControl = this.sendAccessForm.get("password"); + if (passwordControl) { + passwordControl.setErrors({ + invalidPassword: { message: this.i18nService.t("sendPasswordInvalidAskOwner") }, + }); + passwordControl.markAsTouched(); + } + } + // Set auth type to Password (either first time or refresh) this.sendAuthType.set(AuthType.Password); + } else if (e.statusCode === 400 && this.sendAuthType() === AuthType.Password) { + // Server returns 400 for SendAccessResult.PasswordInvalid + const passwordControl = this.sendAccessForm.get("password"); + if (passwordControl) { + passwordControl.setErrors({ + invalidPassword: { message: this.i18nService.t("sendPasswordInvalidAskOwner") }, + }); + passwordControl.markAsTouched(); + } } else if (e.statusCode === 404) { this.unavailable.set(true); } else { @@ -164,22 +194,29 @@ export class SendAuthComponent implements OnInit { this.updatePageTitle(); } else if (emailAndOtpRequired(response.error)) { this.enterOtp.set(true); + if (this.otpSubmitted) { + this.toastService.showToast({ + variant: "error", + title: this.i18nService.t("errorOccurred"), + message: this.i18nService.t("invalidEmailOrVerificationCode"), + }); + } + this.otpSubmitted = true; this.updatePageTitle(); } else if (otpInvalid(response.error)) { this.toastService.showToast({ variant: "error", title: this.i18nService.t("errorOccurred"), - message: this.i18nService.t("invalidVerificationCode"), + message: this.i18nService.t("invalidEmailOrVerificationCode"), }); } else if (passwordHashB64Required(response.error)) { this.sendAuthType.set(AuthType.Password); this.updatePageTitle(); } else if (passwordHashB64Invalid(response.error)) { - this.toastService.showToast({ - variant: "error", - title: this.i18nService.t("errorOccurred"), - message: this.i18nService.t("invalidSendPassword"), + this.sendAccessForm.controls.password?.setErrors({ + invalidPassword: { message: this.i18nService.t("sendPasswordInvalidAskOwner") }, }); + this.sendAccessForm.controls.password?.markAsTouched(); } else if (sendIdInvalid(response.error)) { this.unavailable.set(true); } else { @@ -219,10 +256,12 @@ export class SendAuthComponent implements OnInit { if (this.enterOtp()) { this.anonLayoutWrapperDataService.setAnonLayoutWrapperData({ pageTitle: { key: "enterTheCodeSentToYourEmail" }, + pageSubtitle: this.sendAccessForm.value.email ?? null, }); } else { this.anonLayoutWrapperDataService.setAnonLayoutWrapperData({ pageTitle: { key: "verifyYourEmailToViewThisSend" }, + pageSubtitle: null, }); } } else if (authType === AuthType.Password) { diff --git a/apps/web/src/app/vault/components/vault-items/vault-cipher-row.component.html b/apps/web/src/app/vault/components/vault-items/vault-cipher-row.component.html index 3e62ccfd21d..942cb1cdf2e 100644 --- a/apps/web/src/app/vault/components/vault-items/vault-cipher-row.component.html +++ b/apps/web/src/app/vault/components/vault-items/vault-cipher-row.component.html @@ -15,6 +15,7 @@
    + diff --git a/apps/web/src/app/vault/components/vault-welcome-dialog/vault-welcome-dialog.component.html b/apps/web/src/app/vault/components/vault-welcome-dialog/vault-welcome-dialog.component.html new file mode 100644 index 00000000000..3304fa3e3cc --- /dev/null +++ b/apps/web/src/app/vault/components/vault-welcome-dialog/vault-welcome-dialog.component.html @@ -0,0 +1,27 @@ + +
    + +
    +
    +

    + {{ "vaultWelcomeDialogTitle" | i18n }} +

    +

    + {{ "vaultWelcomeDialogDescription" | i18n }} +

    +
    +
    +
    +
    + + +
    +
    diff --git a/apps/web/src/app/vault/components/vault-welcome-dialog/vault-welcome-dialog.component.spec.ts b/apps/web/src/app/vault/components/vault-welcome-dialog/vault-welcome-dialog.component.spec.ts new file mode 100644 index 00000000000..bc0142b374d --- /dev/null +++ b/apps/web/src/app/vault/components/vault-welcome-dialog/vault-welcome-dialog.component.spec.ts @@ -0,0 +1,87 @@ +import { ComponentFixture, TestBed } from "@angular/core/testing"; +import { BehaviorSubject } from "rxjs"; + +import { Account, AccountService } from "@bitwarden/common/auth/abstractions/account.service"; +import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; +import { UserId } from "@bitwarden/common/types/guid"; +import { DialogRef } from "@bitwarden/components"; +import { StateProvider } from "@bitwarden/state"; + +import { + VaultWelcomeDialogComponent, + VaultWelcomeDialogResult, +} from "./vault-welcome-dialog.component"; + +describe("VaultWelcomeDialogComponent", () => { + let component: VaultWelcomeDialogComponent; + let fixture: ComponentFixture; + + const mockUserId = "user-123" as UserId; + const activeAccount$ = new BehaviorSubject({ + id: mockUserId, + } as Account); + const setUserState = jest.fn().mockResolvedValue([mockUserId, true]); + const close = jest.fn(); + + beforeEach(async () => { + jest.clearAllMocks(); + + await TestBed.configureTestingModule({ + imports: [VaultWelcomeDialogComponent], + providers: [ + { provide: AccountService, useValue: { activeAccount$ } }, + { provide: StateProvider, useValue: { setUserState } }, + { provide: DialogRef, useValue: { close } }, + { provide: I18nService, useValue: { t: (key: string) => key } }, + ], + }).compileComponents(); + + fixture = TestBed.createComponent(VaultWelcomeDialogComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + describe("onDismiss", () => { + it("should set acknowledged state and close with Dismissed result", async () => { + await component["onDismiss"](); + + expect(setUserState).toHaveBeenCalledWith( + expect.objectContaining({ key: "vaultWelcomeDialogAcknowledged" }), + true, + mockUserId, + ); + expect(close).toHaveBeenCalledWith(VaultWelcomeDialogResult.Dismissed); + }); + + it("should throw if no active account", async () => { + activeAccount$.next(null); + + await expect(component["onDismiss"]()).rejects.toThrow("Null or undefined account"); + + expect(setUserState).not.toHaveBeenCalled(); + }); + }); + + describe("onPrimaryCta", () => { + it("should set acknowledged state and close with GetStarted result", async () => { + activeAccount$.next({ id: mockUserId } as Account); + + await component["onPrimaryCta"](); + + expect(setUserState).toHaveBeenCalledWith( + expect.objectContaining({ key: "vaultWelcomeDialogAcknowledged" }), + true, + mockUserId, + ); + expect(close).toHaveBeenCalledWith(VaultWelcomeDialogResult.GetStarted); + }); + + it("should throw if no active account", async () => { + activeAccount$.next(null); + + await expect(component["onPrimaryCta"]()).rejects.toThrow("Null or undefined account"); + + expect(setUserState).not.toHaveBeenCalled(); + }); + }); +}); diff --git a/apps/web/src/app/vault/components/vault-welcome-dialog/vault-welcome-dialog.component.ts b/apps/web/src/app/vault/components/vault-welcome-dialog/vault-welcome-dialog.component.ts new file mode 100644 index 00000000000..d43ea5165f7 --- /dev/null +++ b/apps/web/src/app/vault/components/vault-welcome-dialog/vault-welcome-dialog.component.ts @@ -0,0 +1,69 @@ +import { CommonModule } from "@angular/common"; +import { ChangeDetectionStrategy, Component, inject } from "@angular/core"; +import { firstValueFrom } from "rxjs"; + +import { JslibModule } from "@bitwarden/angular/jslib.module"; +import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; +import { getUserId } from "@bitwarden/common/auth/services/account.service"; +import { + ButtonModule, + DialogModule, + DialogRef, + DialogService, + TypographyModule, + CenterPositionStrategy, +} from "@bitwarden/components"; +import { StateProvider, UserKeyDefinition, VAULT_WELCOME_DIALOG_DISK } from "@bitwarden/state"; + +export const VaultWelcomeDialogResult = { + Dismissed: "dismissed", + GetStarted: "getStarted", +} as const; + +export type VaultWelcomeDialogResult = + (typeof VaultWelcomeDialogResult)[keyof typeof VaultWelcomeDialogResult]; + +const VAULT_WELCOME_DIALOG_ACKNOWLEDGED_KEY = new UserKeyDefinition( + VAULT_WELCOME_DIALOG_DISK, + "vaultWelcomeDialogAcknowledged", + { + deserializer: (value) => value, + clearOn: [], + }, +); + +@Component({ + selector: "app-vault-welcome-dialog", + templateUrl: "./vault-welcome-dialog.component.html", + changeDetection: ChangeDetectionStrategy.OnPush, + standalone: true, + imports: [CommonModule, DialogModule, ButtonModule, TypographyModule, JslibModule], +}) +export class VaultWelcomeDialogComponent { + private accountService = inject(AccountService); + private stateProvider = inject(StateProvider); + + constructor(private dialogRef: DialogRef) {} + + protected async onDismiss(): Promise { + await this.setAcknowledged(); + this.dialogRef.close(VaultWelcomeDialogResult.Dismissed); + } + + protected async onPrimaryCta(): Promise { + await this.setAcknowledged(); + this.dialogRef.close(VaultWelcomeDialogResult.GetStarted); + } + + private async setAcknowledged(): Promise { + const userId = await firstValueFrom(this.accountService.activeAccount$.pipe(getUserId)); + await this.stateProvider.setUserState(VAULT_WELCOME_DIALOG_ACKNOWLEDGED_KEY, true, userId); + } + + static open(dialogService: DialogService): DialogRef { + return dialogService.open(VaultWelcomeDialogComponent, { + disableClose: true, + positionStrategy: new CenterPositionStrategy(), + }); + } +} diff --git a/apps/web/src/app/vault/components/web-vault-extension-prompt/web-vault-extension-prompt-dialog.component.html b/apps/web/src/app/vault/components/web-vault-extension-prompt/web-vault-extension-prompt-dialog.component.html new file mode 100644 index 00000000000..e9932ac9022 --- /dev/null +++ b/apps/web/src/app/vault/components/web-vault-extension-prompt/web-vault-extension-prompt-dialog.component.html @@ -0,0 +1,34 @@ +
    + +
    +

    + {{ "extensionPromptHeading" | i18n }} +

    +

    + {{ "extensionPromptBody" | i18n }} +

    +
    + + + + {{ "downloadExtension" | i18n }} + + +
    +
    +
    diff --git a/apps/web/src/app/vault/components/web-vault-extension-prompt/web-vault-extension-prompt-dialog.component.spec.ts b/apps/web/src/app/vault/components/web-vault-extension-prompt/web-vault-extension-prompt-dialog.component.spec.ts new file mode 100644 index 00000000000..fdf218d8c35 --- /dev/null +++ b/apps/web/src/app/vault/components/web-vault-extension-prompt/web-vault-extension-prompt-dialog.component.spec.ts @@ -0,0 +1,86 @@ +import { ComponentFixture, TestBed } from "@angular/core/testing"; +import { provideNoopAnimations } from "@angular/platform-browser/animations"; +import { mock, MockProxy } from "jest-mock-extended"; + +import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; +import { DeviceType } from "@bitwarden/common/enums"; +import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; +import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; +import { mockAccountServiceWith } from "@bitwarden/common/spec"; +import { UserId } from "@bitwarden/common/types/guid"; +import { DialogRef, DialogService } from "@bitwarden/components"; + +import { WebVaultExtensionPromptService } from "../../services/web-vault-extension-prompt.service"; + +import { WebVaultExtensionPromptDialogComponent } from "./web-vault-extension-prompt-dialog.component"; + +describe("WebVaultExtensionPromptDialogComponent", () => { + let component: WebVaultExtensionPromptDialogComponent; + let fixture: ComponentFixture; + let mockDialogRef: MockProxy>; + + const mockUserId = "test-user-id" as UserId; + + const getDevice = jest.fn(() => DeviceType.ChromeBrowser); + const mockUpdate = jest.fn().mockResolvedValue(undefined); + + const getDialogDismissedState = jest.fn().mockReturnValue({ + update: mockUpdate, + }); + + beforeEach(async () => { + const mockAccountService = mockAccountServiceWith(mockUserId); + mockDialogRef = mock>(); + + await TestBed.configureTestingModule({ + imports: [WebVaultExtensionPromptDialogComponent], + providers: [ + provideNoopAnimations(), + { + provide: PlatformUtilsService, + useValue: { getDevice }, + }, + { provide: I18nService, useValue: { t: (key: string) => key } }, + { provide: AccountService, useValue: mockAccountService }, + { provide: DialogRef, useValue: mockDialogRef }, + { provide: DialogService, useValue: mock() }, + { + provide: WebVaultExtensionPromptService, + useValue: { getDialogDismissedState }, + }, + ], + }).compileComponents(); + + fixture = TestBed.createComponent(WebVaultExtensionPromptDialogComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + describe("ngOnInit", () => { + it("sets webStoreUrl", () => { + expect(getDevice).toHaveBeenCalled(); + + expect(component["webStoreUrl"]).toBe( + "https://chromewebstore.google.com/detail/bitwarden-password-manage/nngceckbapebfimnlniiiahkandclblb", + ); + }); + }); + + describe("dismissPrompt", () => { + it("calls webVaultExtensionPromptService.getDialogDismissedState and updates to true", async () => { + await component.dismissPrompt(); + + expect(getDialogDismissedState).toHaveBeenCalledWith(mockUserId); + expect(mockUpdate).toHaveBeenCalledWith(expect.any(Function)); + + const updateFn = mockUpdate.mock.calls[0][0]; + expect(updateFn()).toBe(true); + }); + + it("closes the dialog", async () => { + await component.dismissPrompt(); + + expect(mockDialogRef.close).toHaveBeenCalled(); + }); + }); +}); diff --git a/apps/web/src/app/vault/components/web-vault-extension-prompt/web-vault-extension-prompt-dialog.component.ts b/apps/web/src/app/vault/components/web-vault-extension-prompt/web-vault-extension-prompt-dialog.component.ts new file mode 100644 index 00000000000..e5dcf5e3cf2 --- /dev/null +++ b/apps/web/src/app/vault/components/web-vault-extension-prompt/web-vault-extension-prompt-dialog.component.ts @@ -0,0 +1,51 @@ +import { CommonModule } from "@angular/common"; +import { Component, ChangeDetectionStrategy, OnInit } from "@angular/core"; +import { firstValueFrom } from "rxjs"; + +import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; +import { getUserId } from "@bitwarden/common/auth/services/account.service"; +import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; +import { getWebStoreUrl } from "@bitwarden/common/vault/utils/get-web-store-url"; +import { + ButtonModule, + DialogModule, + DialogRef, + DialogService, + IconComponent, +} from "@bitwarden/components"; +import { I18nPipe } from "@bitwarden/ui-common"; + +import { WebVaultExtensionPromptService } from "../../services/web-vault-extension-prompt.service"; + +@Component({ + selector: "web-vault-extension-prompt-dialog", + templateUrl: "./web-vault-extension-prompt-dialog.component.html", + imports: [CommonModule, ButtonModule, DialogModule, I18nPipe, IconComponent], + changeDetection: ChangeDetectionStrategy.OnPush, +}) +export class WebVaultExtensionPromptDialogComponent implements OnInit { + constructor( + private platformUtilsService: PlatformUtilsService, + private accountService: AccountService, + private dialogRef: DialogRef, + private webVaultExtensionPromptService: WebVaultExtensionPromptService, + ) {} + + /** Download Url for the extension based on the browser */ + protected webStoreUrl: string = ""; + + ngOnInit(): void { + this.webStoreUrl = getWebStoreUrl(this.platformUtilsService.getDevice()); + } + + async dismissPrompt() { + const userId = await firstValueFrom(this.accountService.activeAccount$.pipe(getUserId)); + await this.webVaultExtensionPromptService.getDialogDismissedState(userId).update(() => true); + this.dialogRef.close(); + } + + /** Opens the web extension prompt generator dialog. */ + static open(dialogService: DialogService) { + return dialogService.open(WebVaultExtensionPromptDialogComponent); + } +} diff --git a/apps/web/src/app/vault/individual-vault/vault.component.ts b/apps/web/src/app/vault/individual-vault/vault.component.ts index 5ff72b0d147..a6b80291647 100644 --- a/apps/web/src/app/vault/individual-vault/vault.component.ts +++ b/apps/web/src/app/vault/individual-vault/vault.component.ts @@ -1,5 +1,5 @@ import { ChangeDetectorRef, Component, NgZone, OnDestroy, OnInit, viewChild } from "@angular/core"; -import { ActivatedRoute, Params, Router } from "@angular/router"; +import { ActivatedRoute, NavigationExtras, Params, Router } from "@angular/router"; import { combineLatest, firstValueFrom, lastValueFrom, Observable, of, Subject } from "rxjs"; import { concatMap, @@ -398,7 +398,7 @@ export class VaultComponent implements OnInit, OnDestr queryParamsHandling: "merge", replaceUrl: true, state: { - focusMainAfterNav: false, + focusAfterNav: false, }, }), ); @@ -877,7 +877,10 @@ export class VaultComponent implements OnInit, OnDestr */ async editCipherAttachments(cipher: C) { if (cipher?.reprompt !== 0 && !(await this.passwordRepromptService.showPasswordPrompt())) { - await this.go({ cipherId: null, itemId: null }); + await this.go( + { cipherId: null, itemId: null }, + this.configureRouterFocusToCipher(typeof cipher?.id === "string" ? cipher.id : undefined), + ); return; } @@ -950,7 +953,10 @@ export class VaultComponent implements OnInit, OnDestr } // Clear the query params when the dialog closes - await this.go({ cipherId: null, itemId: null, action: null }); + await this.go( + { cipherId: null, itemId: null, action: null }, + this.configureRouterFocusToCipher(formConfig.originalCipher?.id), + ); } /** @@ -1010,7 +1016,10 @@ export class VaultComponent implements OnInit, OnDestr !(await this.passwordRepromptService.showPasswordPrompt()) ) { // didn't pass password prompt, so don't open add / edit modal - await this.go({ cipherId: null, itemId: null, action: null }); + await this.go( + { cipherId: null, itemId: null, action: null }, + this.configureRouterFocusToCipher(cipher.id), + ); return; } @@ -1052,7 +1061,10 @@ export class VaultComponent implements OnInit, OnDestr !(await this.passwordRepromptService.showPasswordPrompt()) ) { // Didn't pass password prompt, so don't open add / edit modal. - await this.go({ cipherId: null, itemId: null, action: null }); + await this.go( + { cipherId: null, itemId: null, action: null }, + this.configureRouterFocusToCipher(cipher.id), + ); return; } @@ -1193,8 +1205,7 @@ export class VaultComponent implements OnInit, OnDestr let availableCollections: CollectionView[] = []; const orgId = - this.activeFilter.organizationId || - ciphers.find((c) => c.organizationId !== undefined)?.organizationId; + this.activeFilter.organizationId || ciphers.find((c) => !!c.organizationId)?.organizationId; if (orgId && orgId !== "MyVault") { const organization = this.allOrganizations.find((o) => o.id === orgId); @@ -1553,7 +1564,25 @@ export class VaultComponent implements OnInit, OnDestr this.vaultItemsComponent()?.clearSelection(); } - private async go(queryParams: any = null) { + /** + * Helper function to set up the `state.focusAfterNav` property for dialog router navigation if + * the cipherId exists. If it doesn't exist, returns undefined. + * + * This ensures that when the routed dialog is closed, the focus returns to the cipher button in + * the vault table, which allows keyboard users to continue navigating uninterrupted. + * + * @param cipherId id of cipher + * @returns Partial, specifically the state.focusAfterNav property, or undefined + */ + private configureRouterFocusToCipher(cipherId?: string): Partial | undefined { + if (cipherId) { + return { + state: { focusAfterNav: `#cipher-btn-${cipherId}` }, + }; + } + } + + private async go(queryParams: any = null, navigateOptions?: NavigationExtras) { if (queryParams == null) { queryParams = { favorites: this.activeFilter.isFavorites || null, @@ -1569,6 +1598,7 @@ export class VaultComponent implements OnInit, OnDestr queryParams: queryParams, queryParamsHandling: "merge", replaceUrl: true, + ...navigateOptions, }); } diff --git a/apps/web/src/app/vault/services/web-vault-extension-prompt.service.spec.ts b/apps/web/src/app/vault/services/web-vault-extension-prompt.service.spec.ts new file mode 100644 index 00000000000..4a8865990df --- /dev/null +++ b/apps/web/src/app/vault/services/web-vault-extension-prompt.service.spec.ts @@ -0,0 +1,269 @@ +import { TestBed } from "@angular/core/testing"; +import { BehaviorSubject } from "rxjs"; + +import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; +import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum"; +import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; +import { UserId } from "@bitwarden/common/types/guid"; +import { DialogService } from "@bitwarden/components"; +import { StateProvider } from "@bitwarden/state"; + +import { WebVaultExtensionPromptDialogComponent } from "../components/web-vault-extension-prompt/web-vault-extension-prompt-dialog.component"; + +import { WebBrowserInteractionService } from "./web-browser-interaction.service"; +import { WebVaultExtensionPromptService } from "./web-vault-extension-prompt.service"; + +describe("WebVaultExtensionPromptService", () => { + let service: WebVaultExtensionPromptService; + + const mockUserId = "user-123" as UserId; + const mockAccountCreationDate = new Date("2026-01-15"); + + const getFeatureFlag = jest.fn(); + const extensionInstalled$ = new BehaviorSubject(false); + const mockStateSubject = new BehaviorSubject(false); + const activeAccountSubject = new BehaviorSubject<{ id: UserId; creationDate: Date | null }>({ + id: mockUserId, + creationDate: mockAccountCreationDate, + }); + const getUser = jest.fn().mockReturnValue({ state$: mockStateSubject.asObservable() }); + + beforeEach(() => { + jest.clearAllMocks(); + getFeatureFlag.mockResolvedValue(false); + extensionInstalled$.next(false); + mockStateSubject.next(false); + activeAccountSubject.next({ id: mockUserId, creationDate: mockAccountCreationDate }); + + TestBed.configureTestingModule({ + providers: [ + WebVaultExtensionPromptService, + { + provide: StateProvider, + useValue: { + getUser, + }, + }, + { + provide: WebBrowserInteractionService, + useValue: { + extensionInstalled$: extensionInstalled$.asObservable(), + }, + }, + { + provide: AccountService, + useValue: { + activeAccount$: activeAccountSubject.asObservable(), + }, + }, + { + provide: ConfigService, + useValue: { + getFeatureFlag, + }, + }, + { + provide: DialogService, + useValue: { + open: jest.fn(), + }, + }, + ], + }); + + service = TestBed.inject(WebVaultExtensionPromptService); + }); + + describe("conditionallyPromptUserForExtension", () => { + it("returns false when feature flag is disabled", async () => { + getFeatureFlag.mockResolvedValueOnce(false); + + const result = await service.conditionallyPromptUserForExtension(mockUserId); + + expect(result).toBe(false); + expect(getFeatureFlag).toHaveBeenCalledWith( + FeatureFlag.PM29438_WelcomeDialogWithExtensionPrompt, + ); + }); + + it("returns false when dialog has been dismissed", async () => { + getFeatureFlag.mockResolvedValueOnce(true); + mockStateSubject.next(true); + extensionInstalled$.next(false); + + const result = await service.conditionallyPromptUserForExtension(mockUserId); + + expect(result).toBe(false); + }); + + it("returns false when profile is not within thresholds (too old)", async () => { + getFeatureFlag + .mockResolvedValueOnce(true) // Main feature flag + .mockResolvedValueOnce(0); // Min age days + mockStateSubject.next(false); + extensionInstalled$.next(false); + const oldAccountDate = new Date("2025-12-01"); // More than 30 days old + activeAccountSubject.next({ id: mockUserId, creationDate: oldAccountDate }); + + const result = await service.conditionallyPromptUserForExtension(mockUserId); + + expect(result).toBe(false); + }); + + it("returns false when profile is not within thresholds (too young)", async () => { + getFeatureFlag + .mockResolvedValueOnce(true) // Main feature flag + .mockResolvedValueOnce(10); // Min age days = 10 + mockStateSubject.next(false); + extensionInstalled$.next(false); + const youngAccountDate = new Date(); // Today + youngAccountDate.setDate(youngAccountDate.getDate() - 5); // 5 days old + activeAccountSubject.next({ id: mockUserId, creationDate: youngAccountDate }); + + const result = await service.conditionallyPromptUserForExtension(mockUserId); + + expect(result).toBe(false); + }); + + it("returns false when extension is installed", async () => { + getFeatureFlag + .mockResolvedValueOnce(true) // Main feature flag + .mockResolvedValueOnce(0); // Min age days + mockStateSubject.next(false); + extensionInstalled$.next(true); + + const result = await service.conditionallyPromptUserForExtension(mockUserId); + + expect(result).toBe(false); + }); + + it("returns true and opens dialog when all conditions are met", async () => { + getFeatureFlag + .mockResolvedValueOnce(true) // Main feature flag + .mockResolvedValueOnce(0); // Min age days + mockStateSubject.next(false); + extensionInstalled$.next(false); + + // Set account creation date to be within threshold (15 days old) + const validCreationDate = new Date(); + validCreationDate.setDate(validCreationDate.getDate() - 15); + activeAccountSubject.next({ id: mockUserId, creationDate: validCreationDate }); + + const dialogClosedSubject = new BehaviorSubject(undefined); + const openSpy = jest + .spyOn(WebVaultExtensionPromptDialogComponent, "open") + .mockReturnValue({ closed: dialogClosedSubject.asObservable() } as any); + + const resultPromise = service.conditionallyPromptUserForExtension(mockUserId); + + // Close the dialog + dialogClosedSubject.next(undefined); + + const result = await resultPromise; + + expect(openSpy).toHaveBeenCalledWith(expect.anything()); + expect(result).toBe(true); + }); + }); + + describe("profileIsWithinThresholds", () => { + it("returns false when account is younger than min threshold", async () => { + const minAgeDays = 7; + getFeatureFlag.mockResolvedValueOnce(minAgeDays); + + const recentDate = new Date(); + recentDate.setDate(recentDate.getDate() - 5); // 5 days old + activeAccountSubject.next({ id: mockUserId, creationDate: recentDate }); + + const result = await service["profileIsWithinThresholds"](); + + expect(result).toBe(false); + }); + + it("returns true when account is exactly at min threshold", async () => { + const minAgeDays = 7; + getFeatureFlag.mockResolvedValueOnce(minAgeDays); + + const exactDate = new Date(); + exactDate.setDate(exactDate.getDate() - 7); // Exactly 7 days old + activeAccountSubject.next({ id: mockUserId, creationDate: exactDate }); + + const result = await service["profileIsWithinThresholds"](); + + expect(result).toBe(true); + }); + + it("returns true when account is within the thresholds", async () => { + const minAgeDays = 0; + getFeatureFlag.mockResolvedValueOnce(minAgeDays); + + const validDate = new Date(); + validDate.setDate(validDate.getDate() - 15); // 15 days old (between 0 and 30) + activeAccountSubject.next({ id: mockUserId, creationDate: validDate }); + + const result = await service["profileIsWithinThresholds"](); + + expect(result).toBe(true); + }); + + it("returns false when account is older than max threshold (30 days)", async () => { + const minAgeDays = 0; + getFeatureFlag.mockResolvedValueOnce(minAgeDays); + + const oldDate = new Date(); + oldDate.setDate(oldDate.getDate() - 31); // 31 days old + activeAccountSubject.next({ id: mockUserId, creationDate: oldDate }); + + const result = await service["profileIsWithinThresholds"](); + + expect(result).toBe(false); + }); + + it("returns false when account is exactly 30 days old", async () => { + const minAgeDays = 0; + getFeatureFlag.mockResolvedValueOnce(minAgeDays); + + const exactDate = new Date(); + exactDate.setDate(exactDate.getDate() - 30); // Exactly 30 days old + activeAccountSubject.next({ id: mockUserId, creationDate: exactDate }); + + const result = await service["profileIsWithinThresholds"](); + + expect(result).toBe(false); + }); + + it("uses default min age of 0 when feature flag is null", async () => { + getFeatureFlag.mockResolvedValueOnce(null); + + const recentDate = new Date(); + recentDate.setDate(recentDate.getDate() - 5); // 5 days old + activeAccountSubject.next({ id: mockUserId, creationDate: recentDate }); + + const result = await service["profileIsWithinThresholds"](); + + expect(result).toBe(true); + }); + + it("defaults to false", async () => { + getFeatureFlag.mockResolvedValueOnce(0); + activeAccountSubject.next({ id: mockUserId, creationDate: null }); + + const result = await service["profileIsWithinThresholds"](); + + expect(result).toBe(false); + }); + }); + + describe("getDialogDismissedState", () => { + it("returns the SingleUserState for the dialog dismissed state", () => { + service.getDialogDismissedState(mockUserId); + + expect(getUser).toHaveBeenCalledWith( + mockUserId, + expect.objectContaining({ + key: "vaultWelcomeExtensionDialogDismissed", + }), + ); + }); + }); +}); diff --git a/apps/web/src/app/vault/services/web-vault-extension-prompt.service.ts b/apps/web/src/app/vault/services/web-vault-extension-prompt.service.ts new file mode 100644 index 00000000000..3e13935f94c --- /dev/null +++ b/apps/web/src/app/vault/services/web-vault-extension-prompt.service.ts @@ -0,0 +1,104 @@ +import { inject, Injectable } from "@angular/core"; +import { firstValueFrom, map } from "rxjs"; + +import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; +import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum"; +import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; +import { UserId } from "@bitwarden/common/types/guid"; +import { DialogService } from "@bitwarden/components"; +import { StateProvider, UserKeyDefinition, WELCOME_EXTENSION_DIALOG_DISK } from "@bitwarden/state"; + +import { WebVaultExtensionPromptDialogComponent } from "../components/web-vault-extension-prompt/web-vault-extension-prompt-dialog.component"; + +import { WebBrowserInteractionService } from "./web-browser-interaction.service"; + +export const WELCOME_EXTENSION_DIALOG_DISMISSED = new UserKeyDefinition( + WELCOME_EXTENSION_DIALOG_DISK, + "vaultWelcomeExtensionDialogDismissed", + { + deserializer: (dismissed) => dismissed, + clearOn: [], + }, +); + +@Injectable({ providedIn: "root" }) +export class WebVaultExtensionPromptService { + private stateProvider = inject(StateProvider); + private webBrowserInteractionService = inject(WebBrowserInteractionService); + private accountService = inject(AccountService); + private configService = inject(ConfigService); + private dialogService = inject(DialogService); + + /** + * Conditionally prompts the user to install the web extension + */ + async conditionallyPromptUserForExtension(userId: UserId) { + const featureFlagEnabled = await this.configService.getFeatureFlag( + FeatureFlag.PM29438_WelcomeDialogWithExtensionPrompt, + ); + + if (!featureFlagEnabled) { + return false; + } + + // Extension check takes time, trigger it early + const hasExtensionInstalled = firstValueFrom( + this.webBrowserInteractionService.extensionInstalled$, + ); + + const hasDismissedExtensionPrompt = await firstValueFrom( + this.getDialogDismissedState(userId).state$.pipe(map((dismissed) => dismissed ?? false)), + ); + if (hasDismissedExtensionPrompt) { + return false; + } + + const profileIsWithinThresholds = await this.profileIsWithinThresholds(); + if (!profileIsWithinThresholds) { + return false; + } + + if (await hasExtensionInstalled) { + return false; + } + + const dialogRef = WebVaultExtensionPromptDialogComponent.open(this.dialogService); + await firstValueFrom(dialogRef.closed); + + return true; + } + + /** Returns the SingleUserState for the dialog dismissed state */ + getDialogDismissedState(userId: UserId) { + return this.stateProvider.getUser(userId, WELCOME_EXTENSION_DIALOG_DISMISSED); + } + + /** + * Returns true if the user's profile is within the defined thresholds for showing the extension prompt, false otherwise. + * Thresholds are defined as account age between a configurable number of days and 30 days. + */ + private async profileIsWithinThresholds() { + const creationDate = await firstValueFrom( + this.accountService.activeAccount$.pipe(map((account) => account?.creationDate)), + ); + + // When account or creationDate is not available for some reason, + // default to not showing the prompt to avoid disrupting the user. + if (!creationDate) { + return false; + } + + const minAccountAgeDays = await this.configService.getFeatureFlag( + FeatureFlag.PM29438_DialogWithExtensionPromptAccountAge, + ); + + const now = new Date(); + const accountAgeMs = now.getTime() - creationDate.getTime(); + const accountAgeDays = accountAgeMs / (1000 * 60 * 60 * 24); + + const minAgeDays = minAccountAgeDays ?? 0; + const maxAgeDays = 30; + + return accountAgeDays >= minAgeDays && accountAgeDays < maxAgeDays; + } +} diff --git a/apps/web/src/app/vault/services/web-vault-prompt.service.spec.ts b/apps/web/src/app/vault/services/web-vault-prompt.service.spec.ts index a224b8e7c4b..14bbc1a86d5 100644 --- a/apps/web/src/app/vault/services/web-vault-prompt.service.spec.ts +++ b/apps/web/src/app/vault/services/web-vault-prompt.service.spec.ts @@ -7,7 +7,7 @@ import { PolicyService } from "@bitwarden/common/admin-console/abstractions/poli import { PolicyType } from "@bitwarden/common/admin-console/enums"; import { Organization } from "@bitwarden/common/admin-console/models/domain/organization"; import { Policy } from "@bitwarden/common/admin-console/models/domain/policy"; -import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; +import { Account, AccountService } from "@bitwarden/common/auth/abstractions/account.service"; import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; import { UserId } from "@bitwarden/common/types/guid"; import { DialogRef, DialogService } from "@bitwarden/components"; @@ -20,7 +20,9 @@ import { } from "../../admin-console/organizations/policies"; import { UnifiedUpgradePromptService } from "../../billing/individual/upgrade/services"; +import { WebVaultExtensionPromptService } from "./web-vault-extension-prompt.service"; import { WebVaultPromptService } from "./web-vault-prompt.service"; +import { WelcomeDialogService } from "./welcome-dialog.service"; describe("WebVaultPromptService", () => { let service: WebVaultPromptService; @@ -38,20 +40,34 @@ describe("WebVaultPromptService", () => { ); const upsertAutoConfirm = jest.fn().mockResolvedValue(undefined); const organizations$ = jest.fn().mockReturnValue(of([])); - const displayUpgradePromptConditionally = jest.fn().mockResolvedValue(undefined); + const displayUpgradePromptConditionally = jest.fn().mockResolvedValue(false); const enforceOrganizationDataOwnership = jest.fn().mockResolvedValue(undefined); + const conditionallyShowWelcomeDialog = jest.fn().mockResolvedValue(false); const logError = jest.fn(); + const conditionallyPromptUserForExtension = jest.fn().mockResolvedValue(false); + + let activeAccount$: BehaviorSubject; + + function createAccount(overrides: Partial = {}): Account { + return { + id: mockUserId, + creationDate: new Date(), + ...overrides, + } as Account; + } beforeEach(() => { jest.clearAllMocks(); + activeAccount$ = new BehaviorSubject(createAccount()); + TestBed.configureTestingModule({ providers: [ WebVaultPromptService, { provide: UnifiedUpgradePromptService, useValue: { displayUpgradePromptConditionally } }, { provide: VaultItemsTransferService, useValue: { enforceOrganizationDataOwnership } }, { provide: PolicyService, useValue: { policies$ } }, - { provide: AccountService, useValue: { activeAccount$: of({ id: mockUserId }) } }, + { provide: AccountService, useValue: { activeAccount$ } }, { provide: AutomaticUserConfirmationService, useValue: { configuration$: configurationAutoConfirm$, upsert: upsertAutoConfirm }, @@ -60,6 +76,14 @@ describe("WebVaultPromptService", () => { { provide: ConfigService, useValue: { getFeatureFlag$ } }, { provide: DialogService, useValue: { open } }, { provide: LogService, useValue: { error: logError } }, + { + provide: WebVaultExtensionPromptService, + useValue: { conditionallyPromptUserForExtension }, + }, + { + provide: WelcomeDialogService, + useValue: { conditionallyShowWelcomeDialog, conditionallyPromptUserForExtension }, + }, ], }); @@ -82,11 +106,19 @@ describe("WebVaultPromptService", () => { service["vaultItemTransferService"].enforceOrganizationDataOwnership, ).toHaveBeenCalledWith(mockUserId); }); + + it("calls conditionallyPromptUserForExtension with the userId", async () => { + await service.conditionallyPromptUser(); + + expect( + service["webVaultExtensionPromptService"].conditionallyPromptUserForExtension, + ).toHaveBeenCalledWith(mockUserId); + }); }); describe("setupAutoConfirm", () => { it("shows dialog when all conditions are met", fakeAsync(() => { - getFeatureFlag$.mockReturnValueOnce(of(true)); + getFeatureFlag$.mockReturnValue(of(true)); configurationAutoConfirm$.mockReturnValueOnce( of({ showSetupDialog: true, enabled: false, showBrowserNotification: false }), ); diff --git a/apps/web/src/app/vault/services/web-vault-prompt.service.ts b/apps/web/src/app/vault/services/web-vault-prompt.service.ts index 1774bfcc085..aac30e7d0f4 100644 --- a/apps/web/src/app/vault/services/web-vault-prompt.service.ts +++ b/apps/web/src/app/vault/services/web-vault-prompt.service.ts @@ -20,6 +20,9 @@ import { } from "../../admin-console/organizations/policies"; import { UnifiedUpgradePromptService } from "../../billing/individual/upgrade/services"; +import { WebVaultExtensionPromptService } from "./web-vault-extension-prompt.service"; +import { WelcomeDialogService } from "./welcome-dialog.service"; + @Injectable() export class WebVaultPromptService { private unifiedUpgradePromptService = inject(UnifiedUpgradePromptService); @@ -31,6 +34,8 @@ export class WebVaultPromptService { private configService = inject(ConfigService); private dialogService = inject(DialogService); private logService = inject(LogService); + private webVaultExtensionPromptService = inject(WebVaultExtensionPromptService); + private welcomeDialogService = inject(WelcomeDialogService); private userId$ = this.accountService.activeAccount$.pipe(getUserId); @@ -46,9 +51,15 @@ export class WebVaultPromptService { async conditionallyPromptUser() { const userId = await firstValueFrom(this.userId$); - void this.unifiedUpgradePromptService.displayUpgradePromptConditionally(); + if (await this.unifiedUpgradePromptService.displayUpgradePromptConditionally()) { + return; + } - void this.vaultItemTransferService.enforceOrganizationDataOwnership(userId); + await this.vaultItemTransferService.enforceOrganizationDataOwnership(userId); + + await this.welcomeDialogService.conditionallyShowWelcomeDialog(); + + await this.webVaultExtensionPromptService.conditionallyPromptUserForExtension(userId); this.checkForAutoConfirm(); } diff --git a/apps/web/src/app/vault/services/welcome-dialog.service.spec.ts b/apps/web/src/app/vault/services/welcome-dialog.service.spec.ts new file mode 100644 index 00000000000..752514ca066 --- /dev/null +++ b/apps/web/src/app/vault/services/welcome-dialog.service.spec.ts @@ -0,0 +1,123 @@ +import { TestBed } from "@angular/core/testing"; +import { BehaviorSubject, of } from "rxjs"; + +import { Account, AccountService } from "@bitwarden/common/auth/abstractions/account.service"; +import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum"; +import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; +import { UserId } from "@bitwarden/common/types/guid"; +import { DialogRef, DialogService } from "@bitwarden/components"; +import { StateProvider } from "@bitwarden/state"; + +import { VaultWelcomeDialogComponent } from "../components/vault-welcome-dialog/vault-welcome-dialog.component"; + +import { WelcomeDialogService } from "./welcome-dialog.service"; + +describe("WelcomeDialogService", () => { + let service: WelcomeDialogService; + + const mockUserId = "user-123" as UserId; + + const getFeatureFlag = jest.fn().mockResolvedValue(false); + const getUserState$ = jest.fn().mockReturnValue(of(false)); + const mockDialogOpen = jest.spyOn(VaultWelcomeDialogComponent, "open"); + + let activeAccount$: BehaviorSubject; + + function createAccount(overrides: Partial = {}): Account { + return { + id: mockUserId, + creationDate: new Date(), + ...overrides, + } as Account; + } + + beforeEach(() => { + jest.clearAllMocks(); + mockDialogOpen.mockReset(); + + activeAccount$ = new BehaviorSubject(createAccount()); + + TestBed.configureTestingModule({ + providers: [ + WelcomeDialogService, + { provide: AccountService, useValue: { activeAccount$ } }, + { provide: ConfigService, useValue: { getFeatureFlag } }, + { provide: DialogService, useValue: {} }, + { provide: StateProvider, useValue: { getUserState$ } }, + ], + }); + + service = TestBed.inject(WelcomeDialogService); + }); + + describe("conditionallyShowWelcomeDialog", () => { + it("should not show dialog when no active account", async () => { + activeAccount$.next(null); + + await service.conditionallyShowWelcomeDialog(); + + expect(mockDialogOpen).not.toHaveBeenCalled(); + }); + + it("should not show dialog when feature flag is disabled", async () => { + getFeatureFlag.mockResolvedValueOnce(false); + + await service.conditionallyShowWelcomeDialog(); + + expect(getFeatureFlag).toHaveBeenCalledWith(FeatureFlag.PM29437_WelcomeDialog); + expect(mockDialogOpen).not.toHaveBeenCalled(); + }); + + it("should not show dialog when account has no creation date", async () => { + activeAccount$.next(createAccount({ creationDate: undefined })); + getFeatureFlag.mockResolvedValueOnce(true); + + await service.conditionallyShowWelcomeDialog(); + + expect(mockDialogOpen).not.toHaveBeenCalled(); + }); + + it("should not show dialog when account is older than 30 days", async () => { + const overThirtyDaysAgo = new Date(Date.now() - 1000 * 60 * 60 * 24 * 30 - 1000); + activeAccount$.next(createAccount({ creationDate: overThirtyDaysAgo })); + getFeatureFlag.mockResolvedValueOnce(true); + + await service.conditionallyShowWelcomeDialog(); + + expect(mockDialogOpen).not.toHaveBeenCalled(); + }); + + it("should not show dialog when user has already acknowledged it", async () => { + activeAccount$.next(createAccount({ creationDate: new Date() })); + getFeatureFlag.mockResolvedValueOnce(true); + getUserState$.mockReturnValueOnce(of(true)); + + await service.conditionallyShowWelcomeDialog(); + + expect(mockDialogOpen).not.toHaveBeenCalled(); + }); + + it("should show dialog for new user who has not acknowledged", async () => { + activeAccount$.next(createAccount({ creationDate: new Date() })); + getFeatureFlag.mockResolvedValueOnce(true); + getUserState$.mockReturnValueOnce(of(false)); + mockDialogOpen.mockReturnValue({ closed: of(undefined) } as DialogRef); + + await service.conditionallyShowWelcomeDialog(); + + expect(mockDialogOpen).toHaveBeenCalled(); + }); + + it("should show dialog for account created exactly 30 days ago", async () => { + const exactlyThirtyDaysAgo = new Date(Date.now() - 1000 * 60 * 60 * 24 * 30); + activeAccount$.next(createAccount({ creationDate: exactlyThirtyDaysAgo })); + getFeatureFlag.mockResolvedValueOnce(true); + getUserState$.mockReturnValueOnce(of(false)); + mockDialogOpen.mockReturnValue({ closed: of(undefined) } as DialogRef); + + await service.conditionallyShowWelcomeDialog(); + + expect(mockDialogOpen).toHaveBeenCalled(); + }); + }); +}); diff --git a/apps/web/src/app/vault/services/welcome-dialog.service.ts b/apps/web/src/app/vault/services/welcome-dialog.service.ts new file mode 100644 index 00000000000..25b24b6df2d --- /dev/null +++ b/apps/web/src/app/vault/services/welcome-dialog.service.ts @@ -0,0 +1,72 @@ +import { inject, Injectable } from "@angular/core"; +import { firstValueFrom, map } from "rxjs"; + +import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; +import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum"; +import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; +import { DialogService } from "@bitwarden/components"; +import { StateProvider, UserKeyDefinition, VAULT_WELCOME_DIALOG_DISK } from "@bitwarden/state"; + +import { VaultWelcomeDialogComponent } from "../components/vault-welcome-dialog/vault-welcome-dialog.component"; + +const VAULT_WELCOME_DIALOG_ACKNOWLEDGED_KEY = new UserKeyDefinition( + VAULT_WELCOME_DIALOG_DISK, + "vaultWelcomeDialogAcknowledged", + { + deserializer: (value) => value, + clearOn: [], + }, +); + +const THIRTY_DAY_MS = 1000 * 60 * 60 * 24 * 30; + +@Injectable({ providedIn: "root" }) +export class WelcomeDialogService { + private accountService = inject(AccountService); + private configService = inject(ConfigService); + private dialogService = inject(DialogService); + private stateProvider = inject(StateProvider); + + /** + * Conditionally shows the welcome dialog to new users. + * + * @returns true if the dialog was shown, false otherwise + */ + async conditionallyShowWelcomeDialog() { + const account = await firstValueFrom(this.accountService.activeAccount$); + if (!account) { + return; + } + + const enabled = await this.configService.getFeatureFlag(FeatureFlag.PM29437_WelcomeDialog); + if (!enabled) { + return; + } + + const createdAt = account.creationDate; + if (!createdAt) { + return; + } + + const ageMs = Date.now() - createdAt.getTime(); + const isNewUser = ageMs >= 0 && ageMs <= THIRTY_DAY_MS; + if (!isNewUser) { + return; + } + + const acknowledged = await firstValueFrom( + this.stateProvider + .getUserState$(VAULT_WELCOME_DIALOG_ACKNOWLEDGED_KEY, account.id) + .pipe(map((v) => v ?? false)), + ); + + if (acknowledged) { + return; + } + + const dialogRef = VaultWelcomeDialogComponent.open(this.dialogService); + await firstValueFrom(dialogRef.closed); + + return; + } +} diff --git a/apps/web/src/images/vault/extension-mock-login.png b/apps/web/src/images/vault/extension-mock-login.png new file mode 100644 index 00000000000..e002da6db2d Binary files /dev/null and b/apps/web/src/images/vault/extension-mock-login.png differ diff --git a/apps/web/src/images/welcome-dialog-graphic.png b/apps/web/src/images/welcome-dialog-graphic.png new file mode 100644 index 00000000000..fd2a12c5272 Binary files /dev/null and b/apps/web/src/images/welcome-dialog-graphic.png differ diff --git a/apps/web/src/locales/af/messages.json b/apps/web/src/locales/af/messages.json index eb983fc3512..1e12ff7be2d 100644 --- a/apps/web/src/locales/af/messages.json +++ b/apps/web/src/locales/af/messages.json @@ -4337,6 +4337,15 @@ } } }, + "automaticallyConfirmedUserId": { + "message": "Automatically confirmed user $ID$.", + "placeholders": { + "id": { + "content": "$1", + "example": "John Smith" + } + } + }, "editedUserId": { "message": "Gebruiker $ID$ gewysig.", "placeholders": { @@ -4596,29 +4605,26 @@ "generatingYourAccessIntelligence": { "message": "Generating your Access Intelligence..." }, - "fetchingMemberData": { - "message": "Fetching member data..." - }, - "analyzingPasswordHealth": { - "message": "Analyzing password health..." - }, - "calculatingRiskScores": { - "message": "Calculating risk scores..." - }, - "generatingReportData": { - "message": "Generating report data..." - }, - "savingReport": { - "message": "Saving report..." - }, - "compilingInsights": { - "message": "Compiling insights..." - }, "loadingProgress": { "message": "Loading progress" }, - "thisMightTakeFewMinutes": { - "message": "This might take a few minutes." + "reviewingMemberData": { + "message": "Reviewing member data..." + }, + "analyzingPasswords": { + "message": "Analyzing passwords..." + }, + "calculatingRisks": { + "message": "Calculating risks..." + }, + "generatingReports": { + "message": "Generating reports..." + }, + "compilingInsightsProgress": { + "message": "Compiling insights..." + }, + "reportGenerationDone": { + "message": "Done!" }, "riskInsightsRunReport": { "message": "Run report" @@ -5849,10 +5855,6 @@ "message": "Don't know the password? Ask the sender for the password needed to access this Send.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, - "sendHiddenByDefault": { - "message": "Hierdie send is by verstek versteek. U kan sy sigbaarheid wissel deur die knop hier onder.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, "downloadAttachments": { "message": "Laai aanhegsels af" }, @@ -6145,6 +6147,21 @@ "autoConfirmCheckBoxLabel": { "message": "I accept these risks and policy updates" }, + "autoConfirmEnabledByAdmin": { + "message": "Turned on Automatic user confirmation setting" + }, + "autoConfirmDisabledByAdmin": { + "message": "Turned off Automatic user confirmation setting" + }, + "autoConfirmEnabledByPortal": { + "message": "Added Automatic user confirmation policy" + }, + "autoConfirmDisabledByPortal": { + "message": "Removed Automatic user confirmation policy" + }, + "system": { + "message": "System" + }, "personalOwnership": { "message": "Persoonlike eienaarskap" }, @@ -7400,6 +7417,9 @@ "invalidVerificationCode": { "message": "Ongeldige bevestigingskode" }, + "invalidEmailOrVerificationCode": { + "message": "Invalid email or verification code" + }, "keyConnectorDomain": { "message": "Key Connector domain" }, @@ -12857,6 +12877,21 @@ "storageFullDescription": { "message": "You have used all $GB$ GB of your encrypted storage. To continue storing files, add more storage." }, + "extensionPromptHeading": { + "message": "Get the extension for easy vault access" + }, + "extensionPromptBody": { + "message": "With the browser extension installed, you'll take Bitwarden everywhere online. It'll fill in passwords, so you can log into your accounts with a single click." + }, + "extensionPromptImageAlt": { + "message": "A web browser showing the Bitwarden extension with autofill items for the current webpage." + }, + "skip": { + "message": "Skip" + }, + "downloadExtension": { + "message": "Download extension" + }, "whoCanView": { "message": "Who can view" }, @@ -12869,6 +12904,9 @@ "enterMultipleEmailsSeparatedByComma": { "message": "Enter multiple emails by separating with a comma." }, + "emailsRequiredChangeAccessType": { + "message": "Email verification requires at least one email address. To remove all emails, change the access type above." + }, "emailPlaceholder": { "message": "user@bitwarden.com , user@acme.com" }, @@ -12893,6 +12931,18 @@ "invalidSendPassword": { "message": "Invalid Send password" }, + "vaultWelcomeDialogTitle": { + "message": "You're in! Welcome to Bitwarden" + }, + "vaultWelcomeDialogDescription": { + "message": "Store all your passwords and personal info in your Bitwarden vault. We'll show you around." + }, + "vaultWelcomeDialogPrimaryCta": { + "message": "Start tour" + }, + "vaultWelcomeDialogDismissCta": { + "message": "Skip" + }, "sendPasswordHelperText": { "message": "Individuals will need to enter the password to view this Send", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." @@ -12948,5 +12998,23 @@ }, "paymentMethodUpdateError": { "message": "There was an error updating your payment method." + }, + "sendPasswordInvalidAskOwner": { + "message": "Invalid password. Ask the sender for the password needed to access this Send.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "sendExpiresOn": { + "message": "This Send expires at $TIME$ on $DATE$", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated.", + "placeholders": { + "time": { + "content": "$1", + "example": "10:00 AM" + }, + "date": { + "content": "$2", + "example": "Jan 1, 1970" + } + } } } diff --git a/apps/web/src/locales/ar/messages.json b/apps/web/src/locales/ar/messages.json index 647c4602c17..9ceeb66bf59 100644 --- a/apps/web/src/locales/ar/messages.json +++ b/apps/web/src/locales/ar/messages.json @@ -4337,6 +4337,15 @@ } } }, + "automaticallyConfirmedUserId": { + "message": "Automatically confirmed user $ID$.", + "placeholders": { + "id": { + "content": "$1", + "example": "John Smith" + } + } + }, "editedUserId": { "message": "Edited user $ID$.", "placeholders": { @@ -4596,29 +4605,26 @@ "generatingYourAccessIntelligence": { "message": "Generating your Access Intelligence..." }, - "fetchingMemberData": { - "message": "Fetching member data..." - }, - "analyzingPasswordHealth": { - "message": "Analyzing password health..." - }, - "calculatingRiskScores": { - "message": "Calculating risk scores..." - }, - "generatingReportData": { - "message": "Generating report data..." - }, - "savingReport": { - "message": "Saving report..." - }, - "compilingInsights": { - "message": "Compiling insights..." - }, "loadingProgress": { "message": "Loading progress" }, - "thisMightTakeFewMinutes": { - "message": "This might take a few minutes." + "reviewingMemberData": { + "message": "Reviewing member data..." + }, + "analyzingPasswords": { + "message": "Analyzing passwords..." + }, + "calculatingRisks": { + "message": "Calculating risks..." + }, + "generatingReports": { + "message": "Generating reports..." + }, + "compilingInsightsProgress": { + "message": "Compiling insights..." + }, + "reportGenerationDone": { + "message": "Done!" }, "riskInsightsRunReport": { "message": "Run report" @@ -5849,10 +5855,6 @@ "message": "لا تعرف كلمة المرور؟ اطلب من المرسل كلمة المرور المطلوبة للوصول إلى هذا الإرسال.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, - "sendHiddenByDefault": { - "message": "هذا الإرسال مخفي بشكل افتراضي. يمكنك تبديل الرؤية باستخدام الزر أدناه.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, "downloadAttachments": { "message": "تحميل المرفقات" }, @@ -6145,6 +6147,21 @@ "autoConfirmCheckBoxLabel": { "message": "I accept these risks and policy updates" }, + "autoConfirmEnabledByAdmin": { + "message": "Turned on Automatic user confirmation setting" + }, + "autoConfirmDisabledByAdmin": { + "message": "Turned off Automatic user confirmation setting" + }, + "autoConfirmEnabledByPortal": { + "message": "Added Automatic user confirmation policy" + }, + "autoConfirmDisabledByPortal": { + "message": "Removed Automatic user confirmation policy" + }, + "system": { + "message": "System" + }, "personalOwnership": { "message": "Remove individual vault" }, @@ -7400,6 +7417,9 @@ "invalidVerificationCode": { "message": "رمز التحقق غير صالح" }, + "invalidEmailOrVerificationCode": { + "message": "Invalid email or verification code" + }, "keyConnectorDomain": { "message": "Key Connector domain" }, @@ -12857,6 +12877,21 @@ "storageFullDescription": { "message": "You have used all $GB$ GB of your encrypted storage. To continue storing files, add more storage." }, + "extensionPromptHeading": { + "message": "Get the extension for easy vault access" + }, + "extensionPromptBody": { + "message": "With the browser extension installed, you'll take Bitwarden everywhere online. It'll fill in passwords, so you can log into your accounts with a single click." + }, + "extensionPromptImageAlt": { + "message": "A web browser showing the Bitwarden extension with autofill items for the current webpage." + }, + "skip": { + "message": "Skip" + }, + "downloadExtension": { + "message": "Download extension" + }, "whoCanView": { "message": "Who can view" }, @@ -12869,6 +12904,9 @@ "enterMultipleEmailsSeparatedByComma": { "message": "Enter multiple emails by separating with a comma." }, + "emailsRequiredChangeAccessType": { + "message": "Email verification requires at least one email address. To remove all emails, change the access type above." + }, "emailPlaceholder": { "message": "user@bitwarden.com , user@acme.com" }, @@ -12893,6 +12931,18 @@ "invalidSendPassword": { "message": "Invalid Send password" }, + "vaultWelcomeDialogTitle": { + "message": "You're in! Welcome to Bitwarden" + }, + "vaultWelcomeDialogDescription": { + "message": "Store all your passwords and personal info in your Bitwarden vault. We'll show you around." + }, + "vaultWelcomeDialogPrimaryCta": { + "message": "Start tour" + }, + "vaultWelcomeDialogDismissCta": { + "message": "Skip" + }, "sendPasswordHelperText": { "message": "Individuals will need to enter the password to view this Send", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." @@ -12948,5 +12998,23 @@ }, "paymentMethodUpdateError": { "message": "There was an error updating your payment method." + }, + "sendPasswordInvalidAskOwner": { + "message": "Invalid password. Ask the sender for the password needed to access this Send.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "sendExpiresOn": { + "message": "This Send expires at $TIME$ on $DATE$", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated.", + "placeholders": { + "time": { + "content": "$1", + "example": "10:00 AM" + }, + "date": { + "content": "$2", + "example": "Jan 1, 1970" + } + } } } diff --git a/apps/web/src/locales/az/messages.json b/apps/web/src/locales/az/messages.json index fec06600593..7d25a8a86e0 100644 --- a/apps/web/src/locales/az/messages.json +++ b/apps/web/src/locales/az/messages.json @@ -4337,6 +4337,15 @@ } } }, + "automaticallyConfirmedUserId": { + "message": "Automatically confirmed user $ID$.", + "placeholders": { + "id": { + "content": "$1", + "example": "John Smith" + } + } + }, "editedUserId": { "message": "$ID$ istifadəçisinə düzəliş edildi.", "placeholders": { @@ -4596,29 +4605,26 @@ "generatingYourAccessIntelligence": { "message": "Access Intelligence-niz yaradılır..." }, - "fetchingMemberData": { - "message": "Üzv veriləri alınır..." - }, - "analyzingPasswordHealth": { - "message": "Parol sağlamlığı analiz edirilir..." - }, - "calculatingRiskScores": { - "message": "Risk xalı hesablanır..." - }, - "generatingReportData": { - "message": "Hesabat veriləri yaradılır..." - }, - "savingReport": { - "message": "Hesabat saxlanılır..." - }, - "compilingInsights": { - "message": "Təhlillər şərh edilir..." - }, "loadingProgress": { "message": "İrəliləyiş yüklənir" }, - "thisMightTakeFewMinutes": { - "message": "Bu, bir neçə dəqiqə çəkə bilər." + "reviewingMemberData": { + "message": "Üzv veriləri incələnir..." + }, + "analyzingPasswords": { + "message": "Parollar təhlil edilir..." + }, + "calculatingRisks": { + "message": "Risklər hesablanır..." + }, + "generatingReports": { + "message": "Hesabatlar yaradılır..." + }, + "compilingInsightsProgress": { + "message": "Compiling insights..." + }, + "reportGenerationDone": { + "message": "Done!" }, "riskInsightsRunReport": { "message": "Hesabatı işə sal" @@ -5849,10 +5855,6 @@ "message": "Parolu bilmirsiniz? Bu \"Send\"ə erişmək üçün parolu göndərən şəxsdən istəyin.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, - "sendHiddenByDefault": { - "message": "Bu \"send\" ilkin olaraq gizlidir. Aşağıdakı düyməni istifadə edərək görünməni dəyişdirə bilərsiniz.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, "downloadAttachments": { "message": "Qoşmaları endir" }, @@ -6145,6 +6147,21 @@ "autoConfirmCheckBoxLabel": { "message": "Bu riskləri və siyasət güncəlləmələrini qəbul edirəm" }, + "autoConfirmEnabledByAdmin": { + "message": "Turned on Automatic user confirmation setting" + }, + "autoConfirmDisabledByAdmin": { + "message": "Turned off Automatic user confirmation setting" + }, + "autoConfirmEnabledByPortal": { + "message": "Added Automatic user confirmation policy" + }, + "autoConfirmDisabledByPortal": { + "message": "Removed Automatic user confirmation policy" + }, + "system": { + "message": "System" + }, "personalOwnership": { "message": "Fərdi seyfi xaric et" }, @@ -6439,7 +6456,7 @@ "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "verifyYourEmailToViewThisSend": { - "message": "Verify your email to view this Send", + "message": "Bu \"Send\"ə baxmaq üçün e-poçtunuzu doğrulayın", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "viewSendHiddenEmailWarning": { @@ -7400,6 +7417,9 @@ "invalidVerificationCode": { "message": "Yararsız doğrulama kodu" }, + "invalidEmailOrVerificationCode": { + "message": "Invalid email or verification code" + }, "keyConnectorDomain": { "message": "Key Connector domeni" }, @@ -12857,6 +12877,21 @@ "storageFullDescription": { "message": "Bütün $GB$ GB-lıq şifrələnmiş anbar sahənizi istifadə etmisiniz. Faylları saxlaya bilmək üçün daha çox anbar sahəsi əlavə edin." }, + "extensionPromptHeading": { + "message": "Get the extension for easy vault access" + }, + "extensionPromptBody": { + "message": "With the browser extension installed, you'll take Bitwarden everywhere online. It'll fill in passwords, so you can log into your accounts with a single click." + }, + "extensionPromptImageAlt": { + "message": "A web browser showing the Bitwarden extension with autofill items for the current webpage." + }, + "skip": { + "message": "Skip" + }, + "downloadExtension": { + "message": "Download extension" + }, "whoCanView": { "message": "Kimlər baxa bilər" }, @@ -12869,6 +12904,9 @@ "enterMultipleEmailsSeparatedByComma": { "message": "Birdən çox e-poçtu daxil edərkən vergül istifadə edin." }, + "emailsRequiredChangeAccessType": { + "message": "Email verification requires at least one email address. To remove all emails, change the access type above." + }, "emailPlaceholder": { "message": "user@bitwarden.com , user@acme.com" }, @@ -12893,6 +12931,18 @@ "invalidSendPassword": { "message": "Yararsız Send parolu" }, + "vaultWelcomeDialogTitle": { + "message": "You're in! Welcome to Bitwarden" + }, + "vaultWelcomeDialogDescription": { + "message": "Store all your passwords and personal info in your Bitwarden vault. We'll show you around." + }, + "vaultWelcomeDialogPrimaryCta": { + "message": "Start tour" + }, + "vaultWelcomeDialogDismissCta": { + "message": "Skip" + }, "sendPasswordHelperText": { "message": "Şəxslər, Send-ə baxması üçün parolu daxil etməli olacaqlar", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." @@ -12948,5 +12998,23 @@ }, "paymentMethodUpdateError": { "message": "Ödəniş üsulunuzu güncəlləyərkən xəta baş verdi." + }, + "sendPasswordInvalidAskOwner": { + "message": "Invalid password. Ask the sender for the password needed to access this Send.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "sendExpiresOn": { + "message": "Bu \"Send\"in müddəti bitir: $TIME$ $DATE$", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated.", + "placeholders": { + "time": { + "content": "$1", + "example": "10:00 AM" + }, + "date": { + "content": "$2", + "example": "Jan 1, 1970" + } + } } } diff --git a/apps/web/src/locales/be/messages.json b/apps/web/src/locales/be/messages.json index 4d6fe96c6d2..a0d13b0f5db 100644 --- a/apps/web/src/locales/be/messages.json +++ b/apps/web/src/locales/be/messages.json @@ -4337,6 +4337,15 @@ } } }, + "automaticallyConfirmedUserId": { + "message": "Automatically confirmed user $ID$.", + "placeholders": { + "id": { + "content": "$1", + "example": "John Smith" + } + } + }, "editedUserId": { "message": "Карыстальнік $ID$ адрэдагаваны.", "placeholders": { @@ -4596,29 +4605,26 @@ "generatingYourAccessIntelligence": { "message": "Generating your Access Intelligence..." }, - "fetchingMemberData": { - "message": "Fetching member data..." - }, - "analyzingPasswordHealth": { - "message": "Analyzing password health..." - }, - "calculatingRiskScores": { - "message": "Calculating risk scores..." - }, - "generatingReportData": { - "message": "Generating report data..." - }, - "savingReport": { - "message": "Saving report..." - }, - "compilingInsights": { - "message": "Compiling insights..." - }, "loadingProgress": { "message": "Loading progress" }, - "thisMightTakeFewMinutes": { - "message": "This might take a few minutes." + "reviewingMemberData": { + "message": "Reviewing member data..." + }, + "analyzingPasswords": { + "message": "Analyzing passwords..." + }, + "calculatingRisks": { + "message": "Calculating risks..." + }, + "generatingReports": { + "message": "Generating reports..." + }, + "compilingInsightsProgress": { + "message": "Compiling insights..." + }, + "reportGenerationDone": { + "message": "Done!" }, "riskInsightsRunReport": { "message": "Run report" @@ -5849,10 +5855,6 @@ "message": "Не ведаеце пароль? Спытайце ў адпраўніка пароль, які неабходны для доступу да гэтага Send.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, - "sendHiddenByDefault": { - "message": "Прадвызначана гэты Send схаваны. Вы можаце змяніць яго бачнасць выкарыстоўваючы кнопку ніжэй.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, "downloadAttachments": { "message": "Download attachments" }, @@ -6145,6 +6147,21 @@ "autoConfirmCheckBoxLabel": { "message": "I accept these risks and policy updates" }, + "autoConfirmEnabledByAdmin": { + "message": "Turned on Automatic user confirmation setting" + }, + "autoConfirmDisabledByAdmin": { + "message": "Turned off Automatic user confirmation setting" + }, + "autoConfirmEnabledByPortal": { + "message": "Added Automatic user confirmation policy" + }, + "autoConfirmDisabledByPortal": { + "message": "Removed Automatic user confirmation policy" + }, + "system": { + "message": "System" + }, "personalOwnership": { "message": "Выдаліць асабістае сховішча" }, @@ -7400,6 +7417,9 @@ "invalidVerificationCode": { "message": "Памылковы праверачны код" }, + "invalidEmailOrVerificationCode": { + "message": "Invalid email or verification code" + }, "keyConnectorDomain": { "message": "Key Connector domain" }, @@ -12857,6 +12877,21 @@ "storageFullDescription": { "message": "You have used all $GB$ GB of your encrypted storage. To continue storing files, add more storage." }, + "extensionPromptHeading": { + "message": "Get the extension for easy vault access" + }, + "extensionPromptBody": { + "message": "With the browser extension installed, you'll take Bitwarden everywhere online. It'll fill in passwords, so you can log into your accounts with a single click." + }, + "extensionPromptImageAlt": { + "message": "A web browser showing the Bitwarden extension with autofill items for the current webpage." + }, + "skip": { + "message": "Skip" + }, + "downloadExtension": { + "message": "Download extension" + }, "whoCanView": { "message": "Who can view" }, @@ -12869,6 +12904,9 @@ "enterMultipleEmailsSeparatedByComma": { "message": "Enter multiple emails by separating with a comma." }, + "emailsRequiredChangeAccessType": { + "message": "Email verification requires at least one email address. To remove all emails, change the access type above." + }, "emailPlaceholder": { "message": "user@bitwarden.com , user@acme.com" }, @@ -12893,6 +12931,18 @@ "invalidSendPassword": { "message": "Invalid Send password" }, + "vaultWelcomeDialogTitle": { + "message": "You're in! Welcome to Bitwarden" + }, + "vaultWelcomeDialogDescription": { + "message": "Store all your passwords and personal info in your Bitwarden vault. We'll show you around." + }, + "vaultWelcomeDialogPrimaryCta": { + "message": "Start tour" + }, + "vaultWelcomeDialogDismissCta": { + "message": "Skip" + }, "sendPasswordHelperText": { "message": "Individuals will need to enter the password to view this Send", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." @@ -12948,5 +12998,23 @@ }, "paymentMethodUpdateError": { "message": "There was an error updating your payment method." + }, + "sendPasswordInvalidAskOwner": { + "message": "Invalid password. Ask the sender for the password needed to access this Send.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "sendExpiresOn": { + "message": "This Send expires at $TIME$ on $DATE$", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated.", + "placeholders": { + "time": { + "content": "$1", + "example": "10:00 AM" + }, + "date": { + "content": "$2", + "example": "Jan 1, 1970" + } + } } } diff --git a/apps/web/src/locales/bg/messages.json b/apps/web/src/locales/bg/messages.json index 7b5ee07efe1..caef0b4869b 100644 --- a/apps/web/src/locales/bg/messages.json +++ b/apps/web/src/locales/bg/messages.json @@ -4337,6 +4337,15 @@ } } }, + "automaticallyConfirmedUserId": { + "message": "Автоматично потвърден потребител: $ID$.", + "placeholders": { + "id": { + "content": "$1", + "example": "John Smith" + } + } + }, "editedUserId": { "message": "Потребител № $ID$ е редактиран.", "placeholders": { @@ -4596,29 +4605,26 @@ "generatingYourAccessIntelligence": { "message": "Създаване на Вашия анализ на достъпа…" }, - "fetchingMemberData": { - "message": "Извличане на данните за членовете…" - }, - "analyzingPasswordHealth": { - "message": "Анализиране на състоянието на паролите…" - }, - "calculatingRiskScores": { - "message": "Изчисляване на оценките на риска…" - }, - "generatingReportData": { - "message": "Създаване на данните за доклада…" - }, - "savingReport": { - "message": "Запазване на доклада…" - }, - "compilingInsights": { - "message": "Събиране на подробности…" - }, "loadingProgress": { "message": "Зареждане на напредъка" }, - "thisMightTakeFewMinutes": { - "message": "Това може да отнеме няколко минути." + "reviewingMemberData": { + "message": "Преглеждане на данните за членовете…" + }, + "analyzingPasswords": { + "message": "Анализиране на паролите…" + }, + "calculatingRisks": { + "message": "Изчисляване на рисковете…" + }, + "generatingReports": { + "message": "Създаване на доклади…" + }, + "compilingInsightsProgress": { + "message": "Събиране на подробности…" + }, + "reportGenerationDone": { + "message": "Готово!" }, "riskInsightsRunReport": { "message": "Изпълнение на доклада" @@ -5849,10 +5855,6 @@ "message": "Ако не знаете паролата, поискайте от изпращача да ви я даде.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, - "sendHiddenByDefault": { - "message": "Стандартно изпращането е скрито. Може да промените това като натиснете бутона по-долу.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, "downloadAttachments": { "message": "Сваляне на прикачените файлове" }, @@ -6145,6 +6147,21 @@ "autoConfirmCheckBoxLabel": { "message": "Приемам тези рискове и промени в политиката" }, + "autoConfirmEnabledByAdmin": { + "message": "Настройката за автоматично потвърждаване на потребители е включена" + }, + "autoConfirmDisabledByAdmin": { + "message": "Настройката за автоматично потвърждаване на потребители е изключена" + }, + "autoConfirmEnabledByPortal": { + "message": "Добавена е политика за автоматично потвърждаване на потребителите" + }, + "autoConfirmDisabledByPortal": { + "message": "Премахната е политика за автоматично потвърждаване на потребителите" + }, + "system": { + "message": "Система" + }, "personalOwnership": { "message": "Индивидуално притежание" }, @@ -7400,6 +7417,9 @@ "invalidVerificationCode": { "message": "Грешен код за потвърждаване" }, + "invalidEmailOrVerificationCode": { + "message": "Грешна е-поща или код за потвърждаване" + }, "keyConnectorDomain": { "message": "Домейн на конектора за ключове" }, @@ -12857,6 +12877,21 @@ "storageFullDescription": { "message": "Използвали сте всичките си $GB$ GB от наличното си място за съхранение на шифровани данни. Ако искате да продължите да добавяте файлове, добавете повече място за съхранение." }, + "extensionPromptHeading": { + "message": "Инсталирайте добавката за по-лесен достъп до трезора си" + }, + "extensionPromptBody": { + "message": "Когато добавката е инсталирана, Битуорден ще бъде винаги лесно достъпен във всички уеб сайтове. С нея можете да попълвате паролите автоматично и да се вписвате с едно щракване на мишката." + }, + "extensionPromptImageAlt": { + "message": "Уеб браузър показващ добавката на Битуорден с елементи за автоматично попълване за текущата уеб страница." + }, + "skip": { + "message": "Пропускане" + }, + "downloadExtension": { + "message": "Сваляне на добавката" + }, "whoCanView": { "message": "Кой може да преглежда" }, @@ -12869,6 +12904,9 @@ "enterMultipleEmailsSeparatedByComma": { "message": "Можете да въведете повече е-пощи, като ги разделите със запетая." }, + "emailsRequiredChangeAccessType": { + "message": "Потвърждаването на е-пощата изисква да е наличен поне един адрес на е-поща. Ако искате да премахнете всички е-пощи, променете начина за достъп по-горе." + }, "emailPlaceholder": { "message": "потребител@bitwarden.com , потребител@acme.com" }, @@ -12893,6 +12931,18 @@ "invalidSendPassword": { "message": "Неправилна парола за Изпращане" }, + "vaultWelcomeDialogTitle": { + "message": "Влязохте! Добре дошли в Битуорден!" + }, + "vaultWelcomeDialogDescription": { + "message": "Съхранявайте всичките си пароли и лични данни в трезора си в Битуорден. Нека Ви разведем!" + }, + "vaultWelcomeDialogPrimaryCta": { + "message": "Начало на обиколката" + }, + "vaultWelcomeDialogDismissCta": { + "message": "Пропускане" + }, "sendPasswordHelperText": { "message": "Хората ще трябва да въведат паролата, за да видят това Изпращане", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." @@ -12948,5 +12998,23 @@ }, "paymentMethodUpdateError": { "message": "Възникна грешка при обновяването на разплащателния метод." + }, + "sendPasswordInvalidAskOwner": { + "message": "Неправилна парола. Попитайте изпращача за паролата за достъп до това Изпращане.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "sendExpiresOn": { + "message": "Това Изпращане изтича в $TIME$ на $DATE$", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated.", + "placeholders": { + "time": { + "content": "$1", + "example": "10:00 AM" + }, + "date": { + "content": "$2", + "example": "Jan 1, 1970" + } + } } } diff --git a/apps/web/src/locales/bn/messages.json b/apps/web/src/locales/bn/messages.json index 9935cc538a1..7dd3573e960 100644 --- a/apps/web/src/locales/bn/messages.json +++ b/apps/web/src/locales/bn/messages.json @@ -4337,6 +4337,15 @@ } } }, + "automaticallyConfirmedUserId": { + "message": "Automatically confirmed user $ID$.", + "placeholders": { + "id": { + "content": "$1", + "example": "John Smith" + } + } + }, "editedUserId": { "message": "Edited user $ID$.", "placeholders": { @@ -4596,29 +4605,26 @@ "generatingYourAccessIntelligence": { "message": "Generating your Access Intelligence..." }, - "fetchingMemberData": { - "message": "Fetching member data..." - }, - "analyzingPasswordHealth": { - "message": "Analyzing password health..." - }, - "calculatingRiskScores": { - "message": "Calculating risk scores..." - }, - "generatingReportData": { - "message": "Generating report data..." - }, - "savingReport": { - "message": "Saving report..." - }, - "compilingInsights": { - "message": "Compiling insights..." - }, "loadingProgress": { "message": "Loading progress" }, - "thisMightTakeFewMinutes": { - "message": "This might take a few minutes." + "reviewingMemberData": { + "message": "Reviewing member data..." + }, + "analyzingPasswords": { + "message": "Analyzing passwords..." + }, + "calculatingRisks": { + "message": "Calculating risks..." + }, + "generatingReports": { + "message": "Generating reports..." + }, + "compilingInsightsProgress": { + "message": "Compiling insights..." + }, + "reportGenerationDone": { + "message": "Done!" }, "riskInsightsRunReport": { "message": "Run report" @@ -5849,10 +5855,6 @@ "message": "Don't know the password? Ask the sender for the password needed to access this Send.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, - "sendHiddenByDefault": { - "message": "This Send is hidden by default. You can toggle its visibility using the button below.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, "downloadAttachments": { "message": "Download attachments" }, @@ -6145,6 +6147,21 @@ "autoConfirmCheckBoxLabel": { "message": "I accept these risks and policy updates" }, + "autoConfirmEnabledByAdmin": { + "message": "Turned on Automatic user confirmation setting" + }, + "autoConfirmDisabledByAdmin": { + "message": "Turned off Automatic user confirmation setting" + }, + "autoConfirmEnabledByPortal": { + "message": "Added Automatic user confirmation policy" + }, + "autoConfirmDisabledByPortal": { + "message": "Removed Automatic user confirmation policy" + }, + "system": { + "message": "System" + }, "personalOwnership": { "message": "Remove individual vault" }, @@ -7400,6 +7417,9 @@ "invalidVerificationCode": { "message": "Invalid verification code" }, + "invalidEmailOrVerificationCode": { + "message": "Invalid email or verification code" + }, "keyConnectorDomain": { "message": "Key Connector domain" }, @@ -12857,6 +12877,21 @@ "storageFullDescription": { "message": "You have used all $GB$ GB of your encrypted storage. To continue storing files, add more storage." }, + "extensionPromptHeading": { + "message": "Get the extension for easy vault access" + }, + "extensionPromptBody": { + "message": "With the browser extension installed, you'll take Bitwarden everywhere online. It'll fill in passwords, so you can log into your accounts with a single click." + }, + "extensionPromptImageAlt": { + "message": "A web browser showing the Bitwarden extension with autofill items for the current webpage." + }, + "skip": { + "message": "Skip" + }, + "downloadExtension": { + "message": "Download extension" + }, "whoCanView": { "message": "Who can view" }, @@ -12869,6 +12904,9 @@ "enterMultipleEmailsSeparatedByComma": { "message": "Enter multiple emails by separating with a comma." }, + "emailsRequiredChangeAccessType": { + "message": "Email verification requires at least one email address. To remove all emails, change the access type above." + }, "emailPlaceholder": { "message": "user@bitwarden.com , user@acme.com" }, @@ -12893,6 +12931,18 @@ "invalidSendPassword": { "message": "Invalid Send password" }, + "vaultWelcomeDialogTitle": { + "message": "You're in! Welcome to Bitwarden" + }, + "vaultWelcomeDialogDescription": { + "message": "Store all your passwords and personal info in your Bitwarden vault. We'll show you around." + }, + "vaultWelcomeDialogPrimaryCta": { + "message": "Start tour" + }, + "vaultWelcomeDialogDismissCta": { + "message": "Skip" + }, "sendPasswordHelperText": { "message": "Individuals will need to enter the password to view this Send", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." @@ -12948,5 +12998,23 @@ }, "paymentMethodUpdateError": { "message": "There was an error updating your payment method." + }, + "sendPasswordInvalidAskOwner": { + "message": "Invalid password. Ask the sender for the password needed to access this Send.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "sendExpiresOn": { + "message": "This Send expires at $TIME$ on $DATE$", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated.", + "placeholders": { + "time": { + "content": "$1", + "example": "10:00 AM" + }, + "date": { + "content": "$2", + "example": "Jan 1, 1970" + } + } } } diff --git a/apps/web/src/locales/bs/messages.json b/apps/web/src/locales/bs/messages.json index dda9249b383..8aea40fb633 100644 --- a/apps/web/src/locales/bs/messages.json +++ b/apps/web/src/locales/bs/messages.json @@ -4337,6 +4337,15 @@ } } }, + "automaticallyConfirmedUserId": { + "message": "Automatically confirmed user $ID$.", + "placeholders": { + "id": { + "content": "$1", + "example": "John Smith" + } + } + }, "editedUserId": { "message": "Edited user $ID$.", "placeholders": { @@ -4596,29 +4605,26 @@ "generatingYourAccessIntelligence": { "message": "Generating your Access Intelligence..." }, - "fetchingMemberData": { - "message": "Fetching member data..." - }, - "analyzingPasswordHealth": { - "message": "Analyzing password health..." - }, - "calculatingRiskScores": { - "message": "Calculating risk scores..." - }, - "generatingReportData": { - "message": "Generating report data..." - }, - "savingReport": { - "message": "Saving report..." - }, - "compilingInsights": { - "message": "Compiling insights..." - }, "loadingProgress": { "message": "Loading progress" }, - "thisMightTakeFewMinutes": { - "message": "This might take a few minutes." + "reviewingMemberData": { + "message": "Reviewing member data..." + }, + "analyzingPasswords": { + "message": "Analyzing passwords..." + }, + "calculatingRisks": { + "message": "Calculating risks..." + }, + "generatingReports": { + "message": "Generating reports..." + }, + "compilingInsightsProgress": { + "message": "Compiling insights..." + }, + "reportGenerationDone": { + "message": "Done!" }, "riskInsightsRunReport": { "message": "Run report" @@ -5849,10 +5855,6 @@ "message": "Don't know the password? Ask the sender for the password needed to access this Send.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, - "sendHiddenByDefault": { - "message": "This Send is hidden by default. You can toggle its visibility using the button below.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, "downloadAttachments": { "message": "Download attachments" }, @@ -6145,6 +6147,21 @@ "autoConfirmCheckBoxLabel": { "message": "I accept these risks and policy updates" }, + "autoConfirmEnabledByAdmin": { + "message": "Turned on Automatic user confirmation setting" + }, + "autoConfirmDisabledByAdmin": { + "message": "Turned off Automatic user confirmation setting" + }, + "autoConfirmEnabledByPortal": { + "message": "Added Automatic user confirmation policy" + }, + "autoConfirmDisabledByPortal": { + "message": "Removed Automatic user confirmation policy" + }, + "system": { + "message": "System" + }, "personalOwnership": { "message": "Remove individual vault" }, @@ -7400,6 +7417,9 @@ "invalidVerificationCode": { "message": "Invalid verification code" }, + "invalidEmailOrVerificationCode": { + "message": "Invalid email or verification code" + }, "keyConnectorDomain": { "message": "Key Connector domain" }, @@ -12857,6 +12877,21 @@ "storageFullDescription": { "message": "You have used all $GB$ GB of your encrypted storage. To continue storing files, add more storage." }, + "extensionPromptHeading": { + "message": "Get the extension for easy vault access" + }, + "extensionPromptBody": { + "message": "With the browser extension installed, you'll take Bitwarden everywhere online. It'll fill in passwords, so you can log into your accounts with a single click." + }, + "extensionPromptImageAlt": { + "message": "A web browser showing the Bitwarden extension with autofill items for the current webpage." + }, + "skip": { + "message": "Skip" + }, + "downloadExtension": { + "message": "Download extension" + }, "whoCanView": { "message": "Who can view" }, @@ -12869,6 +12904,9 @@ "enterMultipleEmailsSeparatedByComma": { "message": "Enter multiple emails by separating with a comma." }, + "emailsRequiredChangeAccessType": { + "message": "Email verification requires at least one email address. To remove all emails, change the access type above." + }, "emailPlaceholder": { "message": "user@bitwarden.com , user@acme.com" }, @@ -12893,6 +12931,18 @@ "invalidSendPassword": { "message": "Invalid Send password" }, + "vaultWelcomeDialogTitle": { + "message": "You're in! Welcome to Bitwarden" + }, + "vaultWelcomeDialogDescription": { + "message": "Store all your passwords and personal info in your Bitwarden vault. We'll show you around." + }, + "vaultWelcomeDialogPrimaryCta": { + "message": "Start tour" + }, + "vaultWelcomeDialogDismissCta": { + "message": "Skip" + }, "sendPasswordHelperText": { "message": "Individuals will need to enter the password to view this Send", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." @@ -12948,5 +12998,23 @@ }, "paymentMethodUpdateError": { "message": "There was an error updating your payment method." + }, + "sendPasswordInvalidAskOwner": { + "message": "Invalid password. Ask the sender for the password needed to access this Send.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "sendExpiresOn": { + "message": "This Send expires at $TIME$ on $DATE$", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated.", + "placeholders": { + "time": { + "content": "$1", + "example": "10:00 AM" + }, + "date": { + "content": "$2", + "example": "Jan 1, 1970" + } + } } } diff --git a/apps/web/src/locales/ca/messages.json b/apps/web/src/locales/ca/messages.json index 642ae65228e..9af40c9f946 100644 --- a/apps/web/src/locales/ca/messages.json +++ b/apps/web/src/locales/ca/messages.json @@ -4337,6 +4337,15 @@ } } }, + "automaticallyConfirmedUserId": { + "message": "Automatically confirmed user $ID$.", + "placeholders": { + "id": { + "content": "$1", + "example": "John Smith" + } + } + }, "editedUserId": { "message": "S'ha editat l'usuari $ID$.", "placeholders": { @@ -4596,29 +4605,26 @@ "generatingYourAccessIntelligence": { "message": "Generating your Access Intelligence..." }, - "fetchingMemberData": { - "message": "Fetching member data..." - }, - "analyzingPasswordHealth": { - "message": "Analyzing password health..." - }, - "calculatingRiskScores": { - "message": "Calculating risk scores..." - }, - "generatingReportData": { - "message": "Generating report data..." - }, - "savingReport": { - "message": "Saving report..." - }, - "compilingInsights": { - "message": "Compiling insights..." - }, "loadingProgress": { "message": "Loading progress" }, - "thisMightTakeFewMinutes": { - "message": "This might take a few minutes." + "reviewingMemberData": { + "message": "Reviewing member data..." + }, + "analyzingPasswords": { + "message": "Analyzing passwords..." + }, + "calculatingRisks": { + "message": "Calculating risks..." + }, + "generatingReports": { + "message": "Generating reports..." + }, + "compilingInsightsProgress": { + "message": "Compiling insights..." + }, + "reportGenerationDone": { + "message": "Done!" }, "riskInsightsRunReport": { "message": "Run report" @@ -5849,10 +5855,6 @@ "message": "No sabeu la contrasenya? Demaneu al remitent la contrasenya necessària per accedir a aquest Send.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, - "sendHiddenByDefault": { - "message": "Aquest Send està ocult per defecte. Podeu canviar la seua visibilitat mitjançant el botó següent.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, "downloadAttachments": { "message": "Baixa els fitxers adjunts" }, @@ -6145,6 +6147,21 @@ "autoConfirmCheckBoxLabel": { "message": "I accept these risks and policy updates" }, + "autoConfirmEnabledByAdmin": { + "message": "Turned on Automatic user confirmation setting" + }, + "autoConfirmDisabledByAdmin": { + "message": "Turned off Automatic user confirmation setting" + }, + "autoConfirmEnabledByPortal": { + "message": "Added Automatic user confirmation policy" + }, + "autoConfirmDisabledByPortal": { + "message": "Removed Automatic user confirmation policy" + }, + "system": { + "message": "System" + }, "personalOwnership": { "message": "Suprimeix la caixa forta individual" }, @@ -7400,6 +7417,9 @@ "invalidVerificationCode": { "message": "Codi de verificació no vàlid" }, + "invalidEmailOrVerificationCode": { + "message": "Invalid email or verification code" + }, "keyConnectorDomain": { "message": "Key Connector domain" }, @@ -12857,6 +12877,21 @@ "storageFullDescription": { "message": "You have used all $GB$ GB of your encrypted storage. To continue storing files, add more storage." }, + "extensionPromptHeading": { + "message": "Get the extension for easy vault access" + }, + "extensionPromptBody": { + "message": "With the browser extension installed, you'll take Bitwarden everywhere online. It'll fill in passwords, so you can log into your accounts with a single click." + }, + "extensionPromptImageAlt": { + "message": "A web browser showing the Bitwarden extension with autofill items for the current webpage." + }, + "skip": { + "message": "Skip" + }, + "downloadExtension": { + "message": "Download extension" + }, "whoCanView": { "message": "Who can view" }, @@ -12869,6 +12904,9 @@ "enterMultipleEmailsSeparatedByComma": { "message": "Enter multiple emails by separating with a comma." }, + "emailsRequiredChangeAccessType": { + "message": "Email verification requires at least one email address. To remove all emails, change the access type above." + }, "emailPlaceholder": { "message": "user@bitwarden.com , user@acme.com" }, @@ -12893,6 +12931,18 @@ "invalidSendPassword": { "message": "Invalid Send password" }, + "vaultWelcomeDialogTitle": { + "message": "You're in! Welcome to Bitwarden" + }, + "vaultWelcomeDialogDescription": { + "message": "Store all your passwords and personal info in your Bitwarden vault. We'll show you around." + }, + "vaultWelcomeDialogPrimaryCta": { + "message": "Start tour" + }, + "vaultWelcomeDialogDismissCta": { + "message": "Skip" + }, "sendPasswordHelperText": { "message": "Individuals will need to enter the password to view this Send", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." @@ -12948,5 +12998,23 @@ }, "paymentMethodUpdateError": { "message": "There was an error updating your payment method." + }, + "sendPasswordInvalidAskOwner": { + "message": "Invalid password. Ask the sender for the password needed to access this Send.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "sendExpiresOn": { + "message": "This Send expires at $TIME$ on $DATE$", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated.", + "placeholders": { + "time": { + "content": "$1", + "example": "10:00 AM" + }, + "date": { + "content": "$2", + "example": "Jan 1, 1970" + } + } } } diff --git a/apps/web/src/locales/cs/messages.json b/apps/web/src/locales/cs/messages.json index ae41eeebfef..469ccc0a072 100644 --- a/apps/web/src/locales/cs/messages.json +++ b/apps/web/src/locales/cs/messages.json @@ -1925,7 +1925,7 @@ "message": "Ověřovací aplikace" }, "authenticatorAppDescV2": { - "message": "Zadejte kód vygenerovaný ověřovací aplikací, jako je Autentikátor Bitwarden.", + "message": "Zadejte kód vygenerovaný ověřovací aplikací, jako je Bitwarden Authenticator.", "description": "'Bitwarden Authenticator' is a product name and should not be translated." }, "yubiKeyTitleV2": { @@ -2704,7 +2704,7 @@ "message": "Pokračovat na bitwarden.com?" }, "twoStepContinueToBitwardenUrlDesc": { - "message": "Autentikátor Bitwarden umožňuje ukládat ověřovací klíče a generovat TOTP kódy pro 2-fázové ověřování. Další informace naleznete na stránkách bitwarden.com" + "message": "Bitwarden Authenticator umožňuje ukládat ověřovací klíče a generovat TOTP kódy pro 2-fázové ověřování. Další informace naleznete na stránkách bitwarden.com" }, "twoStepAuthenticatorScanCodeV2": { "message": "Naskenujte QR kód pomocí Vaší ověřovací aplikace nebo zadejte klíč." @@ -4337,6 +4337,15 @@ } } }, + "automaticallyConfirmedUserId": { + "message": "Automaticky potvrzený uživatel $ID$.", + "placeholders": { + "id": { + "content": "$1", + "example": "John Smith" + } + } + }, "editedUserId": { "message": "Byl upraven uživatel $ID$.", "placeholders": { @@ -4596,29 +4605,26 @@ "generatingYourAccessIntelligence": { "message": "Generování Vaší přístupové inteligence..." }, - "fetchingMemberData": { - "message": "Načítání dat člena..." - }, - "analyzingPasswordHealth": { - "message": "Analyzování zdraví hesla..." - }, - "calculatingRiskScores": { - "message": "Výpočet skóre rizik..." - }, - "generatingReportData": { - "message": "Generování dat hlášení..." - }, - "savingReport": { - "message": "Ukládání hlášení..." - }, - "compilingInsights": { - "message": "Sestavování přehledů..." - }, "loadingProgress": { "message": "Průběh načítání" }, - "thisMightTakeFewMinutes": { - "message": "Může to trvat několik minut." + "reviewingMemberData": { + "message": "Přezkoumávání dat členů..." + }, + "analyzingPasswords": { + "message": "Analyzování hesel..." + }, + "calculatingRisks": { + "message": "Výpočet rizik..." + }, + "generatingReports": { + "message": "Generování zpráv..." + }, + "compilingInsightsProgress": { + "message": "Sestavování přehledů..." + }, + "reportGenerationDone": { + "message": "Hotovo!" }, "riskInsightsRunReport": { "message": "Spustit hlášení" @@ -5849,10 +5855,6 @@ "message": "Neznáte heslo? Požádejte odesílatele o heslo potřebné pro přístup k tomuto Send.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, - "sendHiddenByDefault": { - "message": "Tento Send je ve výchozím nastavení skrytý. Viditelnost můžete přepnout pomocí tlačítka níže.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, "downloadAttachments": { "message": "Stáhnout přílohy" }, @@ -6145,6 +6147,21 @@ "autoConfirmCheckBoxLabel": { "message": "Přijímám tato rizika a aktualizace zásad" }, + "autoConfirmEnabledByAdmin": { + "message": "Zapnuto Automatické potvrzování uživatele" + }, + "autoConfirmDisabledByAdmin": { + "message": "Vypnuto Automatické potvrzování uživatele" + }, + "autoConfirmEnabledByPortal": { + "message": "Přidána zásada automatického potvrzování uživatele" + }, + "autoConfirmDisabledByPortal": { + "message": "Odebrána zásada automatického potvrzování uživatele" + }, + "system": { + "message": "Systém" + }, "personalOwnership": { "message": "Odebrat osobní trezor" }, @@ -6757,7 +6774,7 @@ } }, "bulkResendInvitations": { - "message": "Try sending again" + "message": "Zkusit odeslat znovu" }, "bulkRemovedMessage": { "message": "Úspěšně odebráno" @@ -7400,6 +7417,9 @@ "invalidVerificationCode": { "message": "Neplatný ověřovací kód" }, + "invalidEmailOrVerificationCode": { + "message": "Neplatný e-mail nebo ověřovací kód" + }, "keyConnectorDomain": { "message": "Doména Key Connectoru" }, @@ -12857,6 +12877,21 @@ "storageFullDescription": { "message": "Využili jste celých $GB$ GB Vašeho šifrovaného úložiště. Chcete-li pokračovat v ukládání souborů, přidejte další úložiště." }, + "extensionPromptHeading": { + "message": "Získejte rozšíření pro snadný přístup k trezoru" + }, + "extensionPromptBody": { + "message": "S nainstalovaným rozšířením prohlížeče budete mít Bitwarden všude online. Budou se vyplňovat hesla, takže se můžete přihlásit do svých účtů jediným klepnutím." + }, + "extensionPromptImageAlt": { + "message": "Webový prohlížeč zobrazující rozšíření Bitwarden s položkami automatického vyplňování aktuální webové stránky." + }, + "skip": { + "message": "Přeskočit" + }, + "downloadExtension": { + "message": "Nainstalovat rozšíření" + }, "whoCanView": { "message": "Kdo může zobrazit" }, @@ -12869,6 +12904,9 @@ "enterMultipleEmailsSeparatedByComma": { "message": "Zadejte více e-mailů oddělených čárkou." }, + "emailsRequiredChangeAccessType": { + "message": "Ověření e-mailu vyžaduje alespoň jednu e-mailovou adresu. Chcete-li odebrat všechny emaily, změňte výše uvedený typ přístupu." + }, "emailPlaceholder": { "message": "user@bitwarden.com , user@acme.com" }, @@ -12893,6 +12931,18 @@ "invalidSendPassword": { "message": "Neplatné heslo k Send" }, + "vaultWelcomeDialogTitle": { + "message": "Jste u nás! Vítejte v Bitwardenu" + }, + "vaultWelcomeDialogDescription": { + "message": "Uložte všechna Vaše hesla a osobní informace v trezoru Bitwarden. Provedeme Vás tady." + }, + "vaultWelcomeDialogPrimaryCta": { + "message": "Zahájit prohlídku" + }, + "vaultWelcomeDialogDismissCta": { + "message": "Přeskočit" + }, "sendPasswordHelperText": { "message": "Pro zobrazení tohoto Send budou muset jednotlivci zadat heslo", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." @@ -12948,5 +12998,23 @@ }, "paymentMethodUpdateError": { "message": "Při aktualizaci Vaší platební metody došlo k chybě." + }, + "sendPasswordInvalidAskOwner": { + "message": "Neplatné heslo. Požádejte odesílatele o heslo potřebné pro přístup k tomuto Send.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "sendExpiresOn": { + "message": "Tento Send vyprší v $TIME$ dne $DATE$", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated.", + "placeholders": { + "time": { + "content": "$1", + "example": "10:00 AM" + }, + "date": { + "content": "$2", + "example": "Jan 1, 1970" + } + } } } diff --git a/apps/web/src/locales/cy/messages.json b/apps/web/src/locales/cy/messages.json index 10639a60017..d1252309bfc 100644 --- a/apps/web/src/locales/cy/messages.json +++ b/apps/web/src/locales/cy/messages.json @@ -4337,6 +4337,15 @@ } } }, + "automaticallyConfirmedUserId": { + "message": "Automatically confirmed user $ID$.", + "placeholders": { + "id": { + "content": "$1", + "example": "John Smith" + } + } + }, "editedUserId": { "message": "Edited user $ID$.", "placeholders": { @@ -4596,29 +4605,26 @@ "generatingYourAccessIntelligence": { "message": "Generating your Access Intelligence..." }, - "fetchingMemberData": { - "message": "Fetching member data..." - }, - "analyzingPasswordHealth": { - "message": "Analyzing password health..." - }, - "calculatingRiskScores": { - "message": "Calculating risk scores..." - }, - "generatingReportData": { - "message": "Generating report data..." - }, - "savingReport": { - "message": "Saving report..." - }, - "compilingInsights": { - "message": "Compiling insights..." - }, "loadingProgress": { "message": "Loading progress" }, - "thisMightTakeFewMinutes": { - "message": "This might take a few minutes." + "reviewingMemberData": { + "message": "Reviewing member data..." + }, + "analyzingPasswords": { + "message": "Analyzing passwords..." + }, + "calculatingRisks": { + "message": "Calculating risks..." + }, + "generatingReports": { + "message": "Generating reports..." + }, + "compilingInsightsProgress": { + "message": "Compiling insights..." + }, + "reportGenerationDone": { + "message": "Done!" }, "riskInsightsRunReport": { "message": "Run report" @@ -5849,10 +5855,6 @@ "message": "Don't know the password? Ask the sender for the password needed to access this Send.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, - "sendHiddenByDefault": { - "message": "This Send is hidden by default. You can toggle its visibility using the button below.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, "downloadAttachments": { "message": "Download attachments" }, @@ -6145,6 +6147,21 @@ "autoConfirmCheckBoxLabel": { "message": "I accept these risks and policy updates" }, + "autoConfirmEnabledByAdmin": { + "message": "Turned on Automatic user confirmation setting" + }, + "autoConfirmDisabledByAdmin": { + "message": "Turned off Automatic user confirmation setting" + }, + "autoConfirmEnabledByPortal": { + "message": "Added Automatic user confirmation policy" + }, + "autoConfirmDisabledByPortal": { + "message": "Removed Automatic user confirmation policy" + }, + "system": { + "message": "System" + }, "personalOwnership": { "message": "Remove individual vault" }, @@ -7400,6 +7417,9 @@ "invalidVerificationCode": { "message": "Invalid verification code" }, + "invalidEmailOrVerificationCode": { + "message": "Invalid email or verification code" + }, "keyConnectorDomain": { "message": "Key Connector domain" }, @@ -12857,6 +12877,21 @@ "storageFullDescription": { "message": "You have used all $GB$ GB of your encrypted storage. To continue storing files, add more storage." }, + "extensionPromptHeading": { + "message": "Get the extension for easy vault access" + }, + "extensionPromptBody": { + "message": "With the browser extension installed, you'll take Bitwarden everywhere online. It'll fill in passwords, so you can log into your accounts with a single click." + }, + "extensionPromptImageAlt": { + "message": "A web browser showing the Bitwarden extension with autofill items for the current webpage." + }, + "skip": { + "message": "Skip" + }, + "downloadExtension": { + "message": "Download extension" + }, "whoCanView": { "message": "Who can view" }, @@ -12869,6 +12904,9 @@ "enterMultipleEmailsSeparatedByComma": { "message": "Enter multiple emails by separating with a comma." }, + "emailsRequiredChangeAccessType": { + "message": "Email verification requires at least one email address. To remove all emails, change the access type above." + }, "emailPlaceholder": { "message": "user@bitwarden.com , user@acme.com" }, @@ -12893,6 +12931,18 @@ "invalidSendPassword": { "message": "Invalid Send password" }, + "vaultWelcomeDialogTitle": { + "message": "You're in! Welcome to Bitwarden" + }, + "vaultWelcomeDialogDescription": { + "message": "Store all your passwords and personal info in your Bitwarden vault. We'll show you around." + }, + "vaultWelcomeDialogPrimaryCta": { + "message": "Start tour" + }, + "vaultWelcomeDialogDismissCta": { + "message": "Skip" + }, "sendPasswordHelperText": { "message": "Individuals will need to enter the password to view this Send", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." @@ -12948,5 +12998,23 @@ }, "paymentMethodUpdateError": { "message": "There was an error updating your payment method." + }, + "sendPasswordInvalidAskOwner": { + "message": "Invalid password. Ask the sender for the password needed to access this Send.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "sendExpiresOn": { + "message": "This Send expires at $TIME$ on $DATE$", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated.", + "placeholders": { + "time": { + "content": "$1", + "example": "10:00 AM" + }, + "date": { + "content": "$2", + "example": "Jan 1, 1970" + } + } } } diff --git a/apps/web/src/locales/da/messages.json b/apps/web/src/locales/da/messages.json index 2943127dbb8..11b3dc87b89 100644 --- a/apps/web/src/locales/da/messages.json +++ b/apps/web/src/locales/da/messages.json @@ -4337,6 +4337,15 @@ } } }, + "automaticallyConfirmedUserId": { + "message": "Automatically confirmed user $ID$.", + "placeholders": { + "id": { + "content": "$1", + "example": "John Smith" + } + } + }, "editedUserId": { "message": "Redigerede bruger $ID$.", "placeholders": { @@ -4596,29 +4605,26 @@ "generatingYourAccessIntelligence": { "message": "Generating your Access Intelligence..." }, - "fetchingMemberData": { - "message": "Fetching member data..." - }, - "analyzingPasswordHealth": { - "message": "Analyzing password health..." - }, - "calculatingRiskScores": { - "message": "Calculating risk scores..." - }, - "generatingReportData": { - "message": "Generating report data..." - }, - "savingReport": { - "message": "Saving report..." - }, - "compilingInsights": { - "message": "Compiling insights..." - }, "loadingProgress": { "message": "Loading progress" }, - "thisMightTakeFewMinutes": { - "message": "This might take a few minutes." + "reviewingMemberData": { + "message": "Reviewing member data..." + }, + "analyzingPasswords": { + "message": "Analyzing passwords..." + }, + "calculatingRisks": { + "message": "Calculating risks..." + }, + "generatingReports": { + "message": "Generating reports..." + }, + "compilingInsightsProgress": { + "message": "Compiling insights..." + }, + "reportGenerationDone": { + "message": "Done!" }, "riskInsightsRunReport": { "message": "Run report" @@ -5849,10 +5855,6 @@ "message": "Kender ikke adgangskoden? Bed afsenderen om adgangskoden, der kræves for at tilgå denne Send.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, - "sendHiddenByDefault": { - "message": "Denne Send er som standard skjult. Dens synlighed kan ændres vha. knappen nedenfor.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, "downloadAttachments": { "message": "Download vedhæftninger" }, @@ -6145,6 +6147,21 @@ "autoConfirmCheckBoxLabel": { "message": "I accept these risks and policy updates" }, + "autoConfirmEnabledByAdmin": { + "message": "Turned on Automatic user confirmation setting" + }, + "autoConfirmDisabledByAdmin": { + "message": "Turned off Automatic user confirmation setting" + }, + "autoConfirmEnabledByPortal": { + "message": "Added Automatic user confirmation policy" + }, + "autoConfirmDisabledByPortal": { + "message": "Removed Automatic user confirmation policy" + }, + "system": { + "message": "System" + }, "personalOwnership": { "message": "Fjern individuel boks" }, @@ -7400,6 +7417,9 @@ "invalidVerificationCode": { "message": "Ugyldig bekræftelseskode" }, + "invalidEmailOrVerificationCode": { + "message": "Invalid email or verification code" + }, "keyConnectorDomain": { "message": "Key Connector domain" }, @@ -12857,6 +12877,21 @@ "storageFullDescription": { "message": "You have used all $GB$ GB of your encrypted storage. To continue storing files, add more storage." }, + "extensionPromptHeading": { + "message": "Get the extension for easy vault access" + }, + "extensionPromptBody": { + "message": "With the browser extension installed, you'll take Bitwarden everywhere online. It'll fill in passwords, so you can log into your accounts with a single click." + }, + "extensionPromptImageAlt": { + "message": "A web browser showing the Bitwarden extension with autofill items for the current webpage." + }, + "skip": { + "message": "Skip" + }, + "downloadExtension": { + "message": "Download extension" + }, "whoCanView": { "message": "Who can view" }, @@ -12869,6 +12904,9 @@ "enterMultipleEmailsSeparatedByComma": { "message": "Enter multiple emails by separating with a comma." }, + "emailsRequiredChangeAccessType": { + "message": "Email verification requires at least one email address. To remove all emails, change the access type above." + }, "emailPlaceholder": { "message": "user@bitwarden.com , user@acme.com" }, @@ -12893,6 +12931,18 @@ "invalidSendPassword": { "message": "Invalid Send password" }, + "vaultWelcomeDialogTitle": { + "message": "You're in! Welcome to Bitwarden" + }, + "vaultWelcomeDialogDescription": { + "message": "Store all your passwords and personal info in your Bitwarden vault. We'll show you around." + }, + "vaultWelcomeDialogPrimaryCta": { + "message": "Start tour" + }, + "vaultWelcomeDialogDismissCta": { + "message": "Skip" + }, "sendPasswordHelperText": { "message": "Individuals will need to enter the password to view this Send", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." @@ -12948,5 +12998,23 @@ }, "paymentMethodUpdateError": { "message": "There was an error updating your payment method." + }, + "sendPasswordInvalidAskOwner": { + "message": "Invalid password. Ask the sender for the password needed to access this Send.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "sendExpiresOn": { + "message": "This Send expires at $TIME$ on $DATE$", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated.", + "placeholders": { + "time": { + "content": "$1", + "example": "10:00 AM" + }, + "date": { + "content": "$2", + "example": "Jan 1, 1970" + } + } } } diff --git a/apps/web/src/locales/de/messages.json b/apps/web/src/locales/de/messages.json index a89e10c8d45..6ea7e3b91a2 100644 --- a/apps/web/src/locales/de/messages.json +++ b/apps/web/src/locales/de/messages.json @@ -269,7 +269,7 @@ } }, "numCriticalApplicationsMarkedSuccess": { - "message": "$COUNT$ applications marked critical", + "message": "$COUNT$ Anwendungen als kritisch markiert", "placeholders": { "count": { "content": "$1", @@ -278,7 +278,7 @@ } }, "numApplicationsUnmarkedCriticalSuccess": { - "message": "$COUNT$ applications marked not critical", + "message": "$COUNT$ Anwendungen als nicht-kritisch markiert", "placeholders": { "count": { "content": "$1", @@ -4337,6 +4337,15 @@ } } }, + "automaticallyConfirmedUserId": { + "message": "Automatically confirmed user $ID$.", + "placeholders": { + "id": { + "content": "$1", + "example": "John Smith" + } + } + }, "editedUserId": { "message": "Benutzer $ID$ bearbeitet.", "placeholders": { @@ -4596,29 +4605,26 @@ "generatingYourAccessIntelligence": { "message": "Deine Access Intelligence wird generiert..." }, - "fetchingMemberData": { - "message": "Mitgliedsdaten werden abgerufen..." - }, - "analyzingPasswordHealth": { - "message": "Passwortsicherheit wird analysiert..." - }, - "calculatingRiskScores": { - "message": "Risikobewertung wird berechnet..." - }, - "generatingReportData": { - "message": "Berichtsdaten werden generiert..." - }, - "savingReport": { - "message": "Bericht wird gespeichert..." - }, - "compilingInsights": { - "message": "Analyse wird zusammengestellt..." - }, "loadingProgress": { "message": "Ladefortschritt" }, - "thisMightTakeFewMinutes": { - "message": "Dies kann einige Minuten dauern." + "reviewingMemberData": { + "message": "Reviewing member data..." + }, + "analyzingPasswords": { + "message": "Passwörter werden analysiert..." + }, + "calculatingRisks": { + "message": "Calculating risks..." + }, + "generatingReports": { + "message": "Berichte werden erstellt ..." + }, + "compilingInsightsProgress": { + "message": "Compiling insights..." + }, + "reportGenerationDone": { + "message": "Fertig!" }, "riskInsightsRunReport": { "message": "Bericht ausführen" @@ -5849,10 +5855,6 @@ "message": "Du kennst das Passwort nicht? Frage den Absender nach dem Passwort, das für dieses Send benötigt wird.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, - "sendHiddenByDefault": { - "message": "Dieses Send ist standardmäßig ausgeblendet. Du kannst die Sichtbarkeit mit dem Button unten umschalten.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, "downloadAttachments": { "message": "Anhänge herunterladen" }, @@ -6145,6 +6147,21 @@ "autoConfirmCheckBoxLabel": { "message": "Ich akzeptiere diese Risiken und Richtlinien-Aktualisierungen" }, + "autoConfirmEnabledByAdmin": { + "message": "Turned on Automatic user confirmation setting" + }, + "autoConfirmDisabledByAdmin": { + "message": "Turned off Automatic user confirmation setting" + }, + "autoConfirmEnabledByPortal": { + "message": "Added Automatic user confirmation policy" + }, + "autoConfirmDisabledByPortal": { + "message": "Removed Automatic user confirmation policy" + }, + "system": { + "message": "System" + }, "personalOwnership": { "message": "Persönlichen Tresor entfernen" }, @@ -6439,7 +6456,7 @@ "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "verifyYourEmailToViewThisSend": { - "message": "Verify your email to view this Send", + "message": "Verifiziere deine E-Mail, um dieses Send anzuzeigen", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "viewSendHiddenEmailWarning": { @@ -6687,7 +6704,7 @@ } }, "reinviteSuccessToast": { - "message": "1 invitation sent" + "message": "1 Einladung gesendet" }, "bulkReinviteSentToast": { "message": "$COUNT$ invitations sent", @@ -7400,6 +7417,9 @@ "invalidVerificationCode": { "message": "Ungültiger Verifizierungscode" }, + "invalidEmailOrVerificationCode": { + "message": "E-Mail oder Verifizierungscode ungültig" + }, "keyConnectorDomain": { "message": "Key Connector-Domain" }, @@ -12857,6 +12877,21 @@ "storageFullDescription": { "message": "Du hast die gesamten $GB$ GB deines verschlüsselten Speichers verwendet. Um mit dem Speichern von Dateien fortzufahren, füge mehr Speicher hinzu." }, + "extensionPromptHeading": { + "message": "Get the extension for easy vault access" + }, + "extensionPromptBody": { + "message": "With the browser extension installed, you'll take Bitwarden everywhere online. It'll fill in passwords, so you can log into your accounts with a single click." + }, + "extensionPromptImageAlt": { + "message": "A web browser showing the Bitwarden extension with autofill items for the current webpage." + }, + "skip": { + "message": "Skip" + }, + "downloadExtension": { + "message": "Download extension" + }, "whoCanView": { "message": "Wer kann das sehen" }, @@ -12869,6 +12904,9 @@ "enterMultipleEmailsSeparatedByComma": { "message": "Gib mehrere E-Mail-Adressen ein, indem du sie mit einem Komma trennst." }, + "emailsRequiredChangeAccessType": { + "message": "E-Mail-Verifizierung erfordert mindestens eine E-Mail-Adresse. Ändere den Zugriffstyp oben, um alle E-Mails zu entfernen." + }, "emailPlaceholder": { "message": "benutzer@bitwarden.com, benutzer@acme.com" }, @@ -12893,6 +12931,18 @@ "invalidSendPassword": { "message": "Ungültiges Send-Passwort" }, + "vaultWelcomeDialogTitle": { + "message": "You're in! Welcome to Bitwarden" + }, + "vaultWelcomeDialogDescription": { + "message": "Store all your passwords and personal info in your Bitwarden vault. We'll show you around." + }, + "vaultWelcomeDialogPrimaryCta": { + "message": "Start tour" + }, + "vaultWelcomeDialogDismissCta": { + "message": "Skip" + }, "sendPasswordHelperText": { "message": "Personen müssen das Passwort eingeben, um dieses Send anzusehen", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." @@ -12948,5 +12998,23 @@ }, "paymentMethodUpdateError": { "message": "Beim Aktualisieren deiner Zahlungsmethode ist ein Fehler aufgetreten." + }, + "sendPasswordInvalidAskOwner": { + "message": "Ungültiges Passwort. Frage den Absender nach dem Passwort, das benötigt wird, um auf dieses Send zuzugreifen.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "sendExpiresOn": { + "message": "This Send expires at $TIME$ on $DATE$", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated.", + "placeholders": { + "time": { + "content": "$1", + "example": "10:00 AM" + }, + "date": { + "content": "$2", + "example": "Jan 1, 1970" + } + } } } diff --git a/apps/web/src/locales/el/messages.json b/apps/web/src/locales/el/messages.json index e084687382a..8b4bc6fc581 100644 --- a/apps/web/src/locales/el/messages.json +++ b/apps/web/src/locales/el/messages.json @@ -4337,6 +4337,15 @@ } } }, + "automaticallyConfirmedUserId": { + "message": "Automatically confirmed user $ID$.", + "placeholders": { + "id": { + "content": "$1", + "example": "John Smith" + } + } + }, "editedUserId": { "message": "Επεξεργασία χρήστη $ID$.", "placeholders": { @@ -4596,29 +4605,26 @@ "generatingYourAccessIntelligence": { "message": "Generating your Access Intelligence..." }, - "fetchingMemberData": { - "message": "Fetching member data..." - }, - "analyzingPasswordHealth": { - "message": "Analyzing password health..." - }, - "calculatingRiskScores": { - "message": "Calculating risk scores..." - }, - "generatingReportData": { - "message": "Generating report data..." - }, - "savingReport": { - "message": "Saving report..." - }, - "compilingInsights": { - "message": "Compiling insights..." - }, "loadingProgress": { "message": "Loading progress" }, - "thisMightTakeFewMinutes": { - "message": "This might take a few minutes." + "reviewingMemberData": { + "message": "Reviewing member data..." + }, + "analyzingPasswords": { + "message": "Analyzing passwords..." + }, + "calculatingRisks": { + "message": "Calculating risks..." + }, + "generatingReports": { + "message": "Generating reports..." + }, + "compilingInsightsProgress": { + "message": "Compiling insights..." + }, + "reportGenerationDone": { + "message": "Done!" }, "riskInsightsRunReport": { "message": "Run report" @@ -5849,10 +5855,6 @@ "message": "Δεν γνωρίζετε τον κωδικό; Ζητήστε από τον αποστολέα τον κωδικό που απαιτείται για την πρόσβαση σε αυτό το Send.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, - "sendHiddenByDefault": { - "message": "Αυτό το send είναι κρυμμένο από προεπιλογή. Μπορείτε να αλλάξετε την ορατότητά του χρησιμοποιώντας το παρακάτω κουμπί.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, "downloadAttachments": { "message": "Λήψη συνημμένων" }, @@ -6145,6 +6147,21 @@ "autoConfirmCheckBoxLabel": { "message": "I accept these risks and policy updates" }, + "autoConfirmEnabledByAdmin": { + "message": "Turned on Automatic user confirmation setting" + }, + "autoConfirmDisabledByAdmin": { + "message": "Turned off Automatic user confirmation setting" + }, + "autoConfirmEnabledByPortal": { + "message": "Added Automatic user confirmation policy" + }, + "autoConfirmDisabledByPortal": { + "message": "Removed Automatic user confirmation policy" + }, + "system": { + "message": "System" + }, "personalOwnership": { "message": "Προσωπική Ιδιοκτησία" }, @@ -7400,6 +7417,9 @@ "invalidVerificationCode": { "message": "Μη έγκυρος κωδικός επαλήθευσης" }, + "invalidEmailOrVerificationCode": { + "message": "Invalid email or verification code" + }, "keyConnectorDomain": { "message": "Key Connector domain" }, @@ -12857,6 +12877,21 @@ "storageFullDescription": { "message": "You have used all $GB$ GB of your encrypted storage. To continue storing files, add more storage." }, + "extensionPromptHeading": { + "message": "Get the extension for easy vault access" + }, + "extensionPromptBody": { + "message": "With the browser extension installed, you'll take Bitwarden everywhere online. It'll fill in passwords, so you can log into your accounts with a single click." + }, + "extensionPromptImageAlt": { + "message": "A web browser showing the Bitwarden extension with autofill items for the current webpage." + }, + "skip": { + "message": "Skip" + }, + "downloadExtension": { + "message": "Download extension" + }, "whoCanView": { "message": "Who can view" }, @@ -12869,6 +12904,9 @@ "enterMultipleEmailsSeparatedByComma": { "message": "Enter multiple emails by separating with a comma." }, + "emailsRequiredChangeAccessType": { + "message": "Email verification requires at least one email address. To remove all emails, change the access type above." + }, "emailPlaceholder": { "message": "user@bitwarden.com , user@acme.com" }, @@ -12893,6 +12931,18 @@ "invalidSendPassword": { "message": "Invalid Send password" }, + "vaultWelcomeDialogTitle": { + "message": "You're in! Welcome to Bitwarden" + }, + "vaultWelcomeDialogDescription": { + "message": "Store all your passwords and personal info in your Bitwarden vault. We'll show you around." + }, + "vaultWelcomeDialogPrimaryCta": { + "message": "Start tour" + }, + "vaultWelcomeDialogDismissCta": { + "message": "Skip" + }, "sendPasswordHelperText": { "message": "Individuals will need to enter the password to view this Send", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." @@ -12948,5 +12998,23 @@ }, "paymentMethodUpdateError": { "message": "There was an error updating your payment method." + }, + "sendPasswordInvalidAskOwner": { + "message": "Invalid password. Ask the sender for the password needed to access this Send.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "sendExpiresOn": { + "message": "This Send expires at $TIME$ on $DATE$", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated.", + "placeholders": { + "time": { + "content": "$1", + "example": "10:00 AM" + }, + "date": { + "content": "$2", + "example": "Jan 1, 1970" + } + } } } diff --git a/apps/web/src/locales/en/messages.json b/apps/web/src/locales/en/messages.json index 970244119f8..7ea2abb5d08 100644 --- a/apps/web/src/locales/en/messages.json +++ b/apps/web/src/locales/en/messages.json @@ -4337,6 +4337,15 @@ } } }, + "automaticallyConfirmedUserId": { + "message": "Automatically confirmed user $ID$.", + "placeholders": { + "id": { + "content": "$1", + "example": "John Smith" + } + } + }, "editedUserId": { "message": "Edited user $ID$.", "placeholders": { @@ -4596,29 +4605,26 @@ "generatingYourAccessIntelligence": { "message": "Generating your Access Intelligence..." }, - "fetchingMemberData": { - "message": "Fetching member data..." - }, - "analyzingPasswordHealth": { - "message": "Analyzing password health..." - }, - "calculatingRiskScores": { - "message": "Calculating risk scores..." - }, - "generatingReportData": { - "message": "Generating report data..." - }, - "savingReport": { - "message": "Saving report..." - }, - "compilingInsights": { - "message": "Compiling insights..." - }, "loadingProgress": { "message": "Loading progress" }, - "thisMightTakeFewMinutes": { - "message": "This might take a few minutes." + "reviewingMemberData": { + "message": "Reviewing member data..." + }, + "analyzingPasswords": { + "message": "Analyzing passwords..." + }, + "calculatingRisks": { + "message": "Calculating risks..." + }, + "generatingReports": { + "message": "Generating reports..." + }, + "compilingInsightsProgress": { + "message": "Compiling insights..." + }, + "reportGenerationDone": { + "message": "Done!" }, "riskInsightsRunReport": { "message": "Run report" @@ -5849,10 +5855,6 @@ "message": "Don't know the password? Ask the sender for the password needed to access this Send.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, - "sendHiddenByDefault": { - "message": "This Send is hidden by default. You can toggle its visibility using the button below.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, "downloadAttachments": { "message": "Download attachments" }, @@ -6145,6 +6147,21 @@ "autoConfirmCheckBoxLabel": { "message": "I accept these risks and policy updates" }, + "autoConfirmEnabledByAdmin": { + "message": "Turned on Automatic user confirmation setting" + }, + "autoConfirmDisabledByAdmin": { + "message": "Turned off Automatic user confirmation setting" + }, + "autoConfirmEnabledByPortal": { + "message": "Added Automatic user confirmation policy" + }, + "autoConfirmDisabledByPortal": { + "message": "Removed Automatic user confirmation policy" + }, + "system": { + "message": "System" + }, "personalOwnership": { "message": "Remove individual vault" }, @@ -7400,6 +7417,9 @@ "invalidVerificationCode": { "message": "Invalid verification code" }, + "invalidEmailOrVerificationCode": { + "message": "Invalid email or verification code" + }, "keyConnectorDomain": { "message": "Key Connector domain" }, @@ -10957,6 +10977,9 @@ "memberAccessReportAuthenticationEnabledFalse": { "message": "Off" }, + "memberAccessReportLoadError": { + "message": "Failed to load the member access report. This may be due to a large organization size or network issue. Please try again or contact support if the problem persists." + }, "kdfIterationRecommends": { "message": "We recommend 600,000 or more" }, @@ -12857,6 +12880,21 @@ "storageFullDescription": { "message": "You have used all $GB$ GB of your encrypted storage. To continue storing files, add more storage." }, + "extensionPromptHeading": { + "message": "Get the extension for easy vault access" + }, + "extensionPromptBody": { + "message": "With the browser extension installed, you'll take Bitwarden everywhere online. It'll fill in passwords, so you can log into your accounts with a single click." + }, + "extensionPromptImageAlt": { + "message": "A web browser showing the Bitwarden extension with autofill items for the current webpage." + }, + "skip": { + "message": "Skip" + }, + "downloadExtension": { + "message": "Download extension" + }, "whoCanView": { "message": "Who can view" }, @@ -12869,6 +12907,9 @@ "enterMultipleEmailsSeparatedByComma": { "message": "Enter multiple emails by separating with a comma." }, + "emailsRequiredChangeAccessType": { + "message": "Email verification requires at least one email address. To remove all emails, change the access type above." + }, "emailPlaceholder": { "message": "user@bitwarden.com , user@acme.com" }, @@ -12893,6 +12934,18 @@ "invalidSendPassword": { "message": "Invalid Send password" }, + "vaultWelcomeDialogTitle": { + "message": "You're in! Welcome to Bitwarden" + }, + "vaultWelcomeDialogDescription": { + "message": "Store all your passwords and personal info in your Bitwarden vault. We'll show you around." + }, + "vaultWelcomeDialogPrimaryCta": { + "message": "Start tour" + }, + "vaultWelcomeDialogDismissCta": { + "message": "Skip" + }, "sendPasswordHelperText": { "message": "Individuals will need to enter the password to view this Send", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." @@ -12949,8 +13002,13 @@ "paymentMethodUpdateError": { "message": "There was an error updating your payment method." }, + "sendPasswordInvalidAskOwner": { + "message": "Invalid password. Ask the sender for the password needed to access this Send.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, "sendExpiresOn": { "message": "This Send expires at $TIME$ on $DATE$", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated.", "placeholders": { "time": { "content": "$1", @@ -12960,7 +13018,6 @@ "content": "$2", "example": "Jan 1, 1970" } - }, - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + } } } diff --git a/apps/web/src/locales/en_GB/messages.json b/apps/web/src/locales/en_GB/messages.json index 78242ac88dd..4990efea695 100644 --- a/apps/web/src/locales/en_GB/messages.json +++ b/apps/web/src/locales/en_GB/messages.json @@ -4337,6 +4337,15 @@ } } }, + "automaticallyConfirmedUserId": { + "message": "Automatically confirmed user $ID$.", + "placeholders": { + "id": { + "content": "$1", + "example": "John Smith" + } + } + }, "editedUserId": { "message": "Edited user $ID$.", "placeholders": { @@ -4596,29 +4605,26 @@ "generatingYourAccessIntelligence": { "message": "Generating your Access Intelligence..." }, - "fetchingMemberData": { - "message": "Fetching member data..." - }, - "analyzingPasswordHealth": { - "message": "Analysing password health..." - }, - "calculatingRiskScores": { - "message": "Calculating risk scores..." - }, - "generatingReportData": { - "message": "Generating report data..." - }, - "savingReport": { - "message": "Saving report..." - }, - "compilingInsights": { - "message": "Compiling insights..." - }, "loadingProgress": { "message": "Loading progress" }, - "thisMightTakeFewMinutes": { - "message": "This might take a few minutes." + "reviewingMemberData": { + "message": "Reviewing member data..." + }, + "analyzingPasswords": { + "message": "Analysing passwords..." + }, + "calculatingRisks": { + "message": "Calculating risks..." + }, + "generatingReports": { + "message": "Generating reports..." + }, + "compilingInsightsProgress": { + "message": "Compiling insights..." + }, + "reportGenerationDone": { + "message": "Done!" }, "riskInsightsRunReport": { "message": "Run report" @@ -5849,10 +5855,6 @@ "message": "Don't know the password? Ask the sender for the password needed to access this Send.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, - "sendHiddenByDefault": { - "message": "This Send is hidden by default. You can toggle its visibility using the button below.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, "downloadAttachments": { "message": "Download attachments" }, @@ -6145,6 +6147,21 @@ "autoConfirmCheckBoxLabel": { "message": "I accept these risks and policy updates" }, + "autoConfirmEnabledByAdmin": { + "message": "Turned on Automatic user confirmation setting" + }, + "autoConfirmDisabledByAdmin": { + "message": "Turned off Automatic user confirmation setting" + }, + "autoConfirmEnabledByPortal": { + "message": "Added Automatic user confirmation policy" + }, + "autoConfirmDisabledByPortal": { + "message": "Removed Automatic user confirmation policy" + }, + "system": { + "message": "System" + }, "personalOwnership": { "message": "Remove individual vault" }, @@ -7400,6 +7417,9 @@ "invalidVerificationCode": { "message": "Invalid verification code" }, + "invalidEmailOrVerificationCode": { + "message": "Invalid email or verification code" + }, "keyConnectorDomain": { "message": "Key Connector domain" }, @@ -12857,6 +12877,21 @@ "storageFullDescription": { "message": "You have used all $GB$ GB of your encrypted storage. To continue storing files, add more storage." }, + "extensionPromptHeading": { + "message": "Get the extension for easy vault access" + }, + "extensionPromptBody": { + "message": "With the browser extension installed, you'll take Bitwarden everywhere online. It'll fill in passwords, so you can log into your accounts with a single click." + }, + "extensionPromptImageAlt": { + "message": "A web browser showing the Bitwarden extension with autofill items for the current webpage." + }, + "skip": { + "message": "Skip" + }, + "downloadExtension": { + "message": "Download extension" + }, "whoCanView": { "message": "Who can view" }, @@ -12869,6 +12904,9 @@ "enterMultipleEmailsSeparatedByComma": { "message": "Enter multiple emails by separating with a comma." }, + "emailsRequiredChangeAccessType": { + "message": "Email verification requires at least one email address. To remove all emails, change the access type above." + }, "emailPlaceholder": { "message": "user@bitwarden.com , user@acme.com" }, @@ -12893,6 +12931,18 @@ "invalidSendPassword": { "message": "Invalid Send password" }, + "vaultWelcomeDialogTitle": { + "message": "You're in! Welcome to Bitwarden" + }, + "vaultWelcomeDialogDescription": { + "message": "Store all your passwords and personal info in your Bitwarden vault. We'll show you around." + }, + "vaultWelcomeDialogPrimaryCta": { + "message": "Start tour" + }, + "vaultWelcomeDialogDismissCta": { + "message": "Skip" + }, "sendPasswordHelperText": { "message": "Individuals will need to enter the password to view this Send", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." @@ -12948,5 +12998,23 @@ }, "paymentMethodUpdateError": { "message": "There was an error updating your payment method." + }, + "sendPasswordInvalidAskOwner": { + "message": "Invalid password. Ask the sender for the password needed to access this Send.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "sendExpiresOn": { + "message": "This Send expires at $TIME$ on $DATE$", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated.", + "placeholders": { + "time": { + "content": "$1", + "example": "10:00 AM" + }, + "date": { + "content": "$2", + "example": "Jan 1, 1970" + } + } } } diff --git a/apps/web/src/locales/en_IN/messages.json b/apps/web/src/locales/en_IN/messages.json index 5d1bf31a336..24414fca4e6 100644 --- a/apps/web/src/locales/en_IN/messages.json +++ b/apps/web/src/locales/en_IN/messages.json @@ -4337,6 +4337,15 @@ } } }, + "automaticallyConfirmedUserId": { + "message": "Automatically confirmed user $ID$.", + "placeholders": { + "id": { + "content": "$1", + "example": "John Smith" + } + } + }, "editedUserId": { "message": "Edited user $ID$.", "placeholders": { @@ -4596,29 +4605,26 @@ "generatingYourAccessIntelligence": { "message": "Generating your Access Intelligence..." }, - "fetchingMemberData": { - "message": "Fetching member data..." - }, - "analyzingPasswordHealth": { - "message": "Analysing password health..." - }, - "calculatingRiskScores": { - "message": "Calculating risk scores..." - }, - "generatingReportData": { - "message": "Generating report data..." - }, - "savingReport": { - "message": "Saving report..." - }, - "compilingInsights": { - "message": "Compiling insights..." - }, "loadingProgress": { "message": "Loading progress" }, - "thisMightTakeFewMinutes": { - "message": "This might take a few minutes." + "reviewingMemberData": { + "message": "Reviewing member data..." + }, + "analyzingPasswords": { + "message": "Analysing passwords..." + }, + "calculatingRisks": { + "message": "Calculating risks..." + }, + "generatingReports": { + "message": "Generating reports..." + }, + "compilingInsightsProgress": { + "message": "Compiling insights..." + }, + "reportGenerationDone": { + "message": "Done!" }, "riskInsightsRunReport": { "message": "Run report" @@ -5849,10 +5855,6 @@ "message": "Don't know the password? Ask the Sender for the password needed to access this Send.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, - "sendHiddenByDefault": { - "message": "This send is hidden by default. You can toggle its visibility using the button below.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, "downloadAttachments": { "message": "Download attachments" }, @@ -6145,6 +6147,21 @@ "autoConfirmCheckBoxLabel": { "message": "I accept these risks and policy updates" }, + "autoConfirmEnabledByAdmin": { + "message": "Turned on Automatic user confirmation setting" + }, + "autoConfirmDisabledByAdmin": { + "message": "Turned off Automatic user confirmation setting" + }, + "autoConfirmEnabledByPortal": { + "message": "Added Automatic user confirmation policy" + }, + "autoConfirmDisabledByPortal": { + "message": "Removed Automatic user confirmation policy" + }, + "system": { + "message": "System" + }, "personalOwnership": { "message": "Personal Ownership" }, @@ -7400,6 +7417,9 @@ "invalidVerificationCode": { "message": "Invalid verification code" }, + "invalidEmailOrVerificationCode": { + "message": "Invalid email or verification code" + }, "keyConnectorDomain": { "message": "Key Connector domain" }, @@ -12857,6 +12877,21 @@ "storageFullDescription": { "message": "You have used all $GB$ GB of your encrypted storage. To continue storing files, add more storage." }, + "extensionPromptHeading": { + "message": "Get the extension for easy vault access" + }, + "extensionPromptBody": { + "message": "With the browser extension installed, you'll take Bitwarden everywhere online. It'll fill in passwords, so you can log into your accounts with a single click." + }, + "extensionPromptImageAlt": { + "message": "A web browser showing the Bitwarden extension with autofill items for the current webpage." + }, + "skip": { + "message": "Skip" + }, + "downloadExtension": { + "message": "Download extension" + }, "whoCanView": { "message": "Who can view" }, @@ -12869,6 +12904,9 @@ "enterMultipleEmailsSeparatedByComma": { "message": "Enter multiple emails by separating with a comma." }, + "emailsRequiredChangeAccessType": { + "message": "Email verification requires at least one email address. To remove all emails, change the access type above." + }, "emailPlaceholder": { "message": "user@bitwarden.com , user@acme.com" }, @@ -12893,6 +12931,18 @@ "invalidSendPassword": { "message": "Invalid Send password" }, + "vaultWelcomeDialogTitle": { + "message": "You're in! Welcome to Bitwarden" + }, + "vaultWelcomeDialogDescription": { + "message": "Store all your passwords and personal info in your Bitwarden vault. We'll show you around." + }, + "vaultWelcomeDialogPrimaryCta": { + "message": "Start tour" + }, + "vaultWelcomeDialogDismissCta": { + "message": "Skip" + }, "sendPasswordHelperText": { "message": "Individuals will need to enter the password to view this Send", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." @@ -12948,5 +12998,23 @@ }, "paymentMethodUpdateError": { "message": "There was an error updating your payment method." + }, + "sendPasswordInvalidAskOwner": { + "message": "Invalid password. Ask the sender for the password needed to access this Send.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "sendExpiresOn": { + "message": "This Send expires at $TIME$ on $DATE$", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated.", + "placeholders": { + "time": { + "content": "$1", + "example": "10:00 AM" + }, + "date": { + "content": "$2", + "example": "Jan 1, 1970" + } + } } } diff --git a/apps/web/src/locales/eo/messages.json b/apps/web/src/locales/eo/messages.json index 1c9f0473adf..2d3f8d29a29 100644 --- a/apps/web/src/locales/eo/messages.json +++ b/apps/web/src/locales/eo/messages.json @@ -4337,6 +4337,15 @@ } } }, + "automaticallyConfirmedUserId": { + "message": "Automatically confirmed user $ID$.", + "placeholders": { + "id": { + "content": "$1", + "example": "John Smith" + } + } + }, "editedUserId": { "message": "Redaktiĝis uzanto $ID$.", "placeholders": { @@ -4596,29 +4605,26 @@ "generatingYourAccessIntelligence": { "message": "Generating your Access Intelligence..." }, - "fetchingMemberData": { - "message": "Fetching member data..." - }, - "analyzingPasswordHealth": { - "message": "Analyzing password health..." - }, - "calculatingRiskScores": { - "message": "Calculating risk scores..." - }, - "generatingReportData": { - "message": "Generating report data..." - }, - "savingReport": { - "message": "Saving report..." - }, - "compilingInsights": { - "message": "Compiling insights..." - }, "loadingProgress": { "message": "Loading progress" }, - "thisMightTakeFewMinutes": { - "message": "This might take a few minutes." + "reviewingMemberData": { + "message": "Reviewing member data..." + }, + "analyzingPasswords": { + "message": "Analyzing passwords..." + }, + "calculatingRisks": { + "message": "Calculating risks..." + }, + "generatingReports": { + "message": "Generating reports..." + }, + "compilingInsightsProgress": { + "message": "Compiling insights..." + }, + "reportGenerationDone": { + "message": "Done!" }, "riskInsightsRunReport": { "message": "Run report" @@ -5849,10 +5855,6 @@ "message": "Ĉu vi ne scias la pasvorton? Petu al la Sendinto la pasvorton bezonatan por aliri ĉi tiun Sendon.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, - "sendHiddenByDefault": { - "message": "Ĉi tiu sendado estas kaŝita defaŭlte. Vi povas ŝalti ĝian videblecon per la suba butono.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, "downloadAttachments": { "message": "Download attachments" }, @@ -6145,6 +6147,21 @@ "autoConfirmCheckBoxLabel": { "message": "I accept these risks and policy updates" }, + "autoConfirmEnabledByAdmin": { + "message": "Turned on Automatic user confirmation setting" + }, + "autoConfirmDisabledByAdmin": { + "message": "Turned off Automatic user confirmation setting" + }, + "autoConfirmEnabledByPortal": { + "message": "Added Automatic user confirmation policy" + }, + "autoConfirmDisabledByPortal": { + "message": "Removed Automatic user confirmation policy" + }, + "system": { + "message": "System" + }, "personalOwnership": { "message": "Persona Posedo" }, @@ -7400,6 +7417,9 @@ "invalidVerificationCode": { "message": "Invalid verification code" }, + "invalidEmailOrVerificationCode": { + "message": "Invalid email or verification code" + }, "keyConnectorDomain": { "message": "Key Connector domain" }, @@ -12857,6 +12877,21 @@ "storageFullDescription": { "message": "You have used all $GB$ GB of your encrypted storage. To continue storing files, add more storage." }, + "extensionPromptHeading": { + "message": "Get the extension for easy vault access" + }, + "extensionPromptBody": { + "message": "With the browser extension installed, you'll take Bitwarden everywhere online. It'll fill in passwords, so you can log into your accounts with a single click." + }, + "extensionPromptImageAlt": { + "message": "A web browser showing the Bitwarden extension with autofill items for the current webpage." + }, + "skip": { + "message": "Skip" + }, + "downloadExtension": { + "message": "Download extension" + }, "whoCanView": { "message": "Who can view" }, @@ -12869,6 +12904,9 @@ "enterMultipleEmailsSeparatedByComma": { "message": "Enter multiple emails by separating with a comma." }, + "emailsRequiredChangeAccessType": { + "message": "Email verification requires at least one email address. To remove all emails, change the access type above." + }, "emailPlaceholder": { "message": "user@bitwarden.com , user@acme.com" }, @@ -12893,6 +12931,18 @@ "invalidSendPassword": { "message": "Invalid Send password" }, + "vaultWelcomeDialogTitle": { + "message": "You're in! Welcome to Bitwarden" + }, + "vaultWelcomeDialogDescription": { + "message": "Store all your passwords and personal info in your Bitwarden vault. We'll show you around." + }, + "vaultWelcomeDialogPrimaryCta": { + "message": "Start tour" + }, + "vaultWelcomeDialogDismissCta": { + "message": "Skip" + }, "sendPasswordHelperText": { "message": "Individuals will need to enter the password to view this Send", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." @@ -12948,5 +12998,23 @@ }, "paymentMethodUpdateError": { "message": "There was an error updating your payment method." + }, + "sendPasswordInvalidAskOwner": { + "message": "Invalid password. Ask the sender for the password needed to access this Send.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "sendExpiresOn": { + "message": "This Send expires at $TIME$ on $DATE$", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated.", + "placeholders": { + "time": { + "content": "$1", + "example": "10:00 AM" + }, + "date": { + "content": "$2", + "example": "Jan 1, 1970" + } + } } } diff --git a/apps/web/src/locales/es/messages.json b/apps/web/src/locales/es/messages.json index 7e8f62b6a3c..6d4d75e20fc 100644 --- a/apps/web/src/locales/es/messages.json +++ b/apps/web/src/locales/es/messages.json @@ -4337,6 +4337,15 @@ } } }, + "automaticallyConfirmedUserId": { + "message": "Automatically confirmed user $ID$.", + "placeholders": { + "id": { + "content": "$1", + "example": "John Smith" + } + } + }, "editedUserId": { "message": "Usuario $ID$ editado.", "placeholders": { @@ -4596,29 +4605,26 @@ "generatingYourAccessIntelligence": { "message": "Generating your Access Intelligence..." }, - "fetchingMemberData": { - "message": "Fetching member data..." - }, - "analyzingPasswordHealth": { - "message": "Analyzing password health..." - }, - "calculatingRiskScores": { - "message": "Calculating risk scores..." - }, - "generatingReportData": { - "message": "Generating report data..." - }, - "savingReport": { - "message": "Saving report..." - }, - "compilingInsights": { - "message": "Compiling insights..." - }, "loadingProgress": { "message": "Loading progress" }, - "thisMightTakeFewMinutes": { - "message": "This might take a few minutes." + "reviewingMemberData": { + "message": "Reviewing member data..." + }, + "analyzingPasswords": { + "message": "Analyzing passwords..." + }, + "calculatingRisks": { + "message": "Calculating risks..." + }, + "generatingReports": { + "message": "Generating reports..." + }, + "compilingInsightsProgress": { + "message": "Compiling insights..." + }, + "reportGenerationDone": { + "message": "Done!" }, "riskInsightsRunReport": { "message": "Run report" @@ -5849,10 +5855,6 @@ "message": "¿No conoce la contraseña? Pídele al remitente la contraseña necesaria para acceder a este enviar.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, - "sendHiddenByDefault": { - "message": "Este Send está oculto por defecto. Puede cambiar su visibilidad usando el botón de abajo.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, "downloadAttachments": { "message": "Descargar archivos adjuntos" }, @@ -6145,6 +6147,21 @@ "autoConfirmCheckBoxLabel": { "message": "I accept these risks and policy updates" }, + "autoConfirmEnabledByAdmin": { + "message": "Turned on Automatic user confirmation setting" + }, + "autoConfirmDisabledByAdmin": { + "message": "Turned off Automatic user confirmation setting" + }, + "autoConfirmEnabledByPortal": { + "message": "Added Automatic user confirmation policy" + }, + "autoConfirmDisabledByPortal": { + "message": "Removed Automatic user confirmation policy" + }, + "system": { + "message": "System" + }, "personalOwnership": { "message": "Propiedad personal" }, @@ -7400,6 +7417,9 @@ "invalidVerificationCode": { "message": "Código de verificación no válido" }, + "invalidEmailOrVerificationCode": { + "message": "Invalid email or verification code" + }, "keyConnectorDomain": { "message": "Key Connector domain" }, @@ -12857,6 +12877,21 @@ "storageFullDescription": { "message": "You have used all $GB$ GB of your encrypted storage. To continue storing files, add more storage." }, + "extensionPromptHeading": { + "message": "Get the extension for easy vault access" + }, + "extensionPromptBody": { + "message": "With the browser extension installed, you'll take Bitwarden everywhere online. It'll fill in passwords, so you can log into your accounts with a single click." + }, + "extensionPromptImageAlt": { + "message": "A web browser showing the Bitwarden extension with autofill items for the current webpage." + }, + "skip": { + "message": "Skip" + }, + "downloadExtension": { + "message": "Download extension" + }, "whoCanView": { "message": "Who can view" }, @@ -12869,6 +12904,9 @@ "enterMultipleEmailsSeparatedByComma": { "message": "Enter multiple emails by separating with a comma." }, + "emailsRequiredChangeAccessType": { + "message": "Email verification requires at least one email address. To remove all emails, change the access type above." + }, "emailPlaceholder": { "message": "user@bitwarden.com , user@acme.com" }, @@ -12893,6 +12931,18 @@ "invalidSendPassword": { "message": "Invalid Send password" }, + "vaultWelcomeDialogTitle": { + "message": "You're in! Welcome to Bitwarden" + }, + "vaultWelcomeDialogDescription": { + "message": "Store all your passwords and personal info in your Bitwarden vault. We'll show you around." + }, + "vaultWelcomeDialogPrimaryCta": { + "message": "Start tour" + }, + "vaultWelcomeDialogDismissCta": { + "message": "Skip" + }, "sendPasswordHelperText": { "message": "Individuals will need to enter the password to view this Send", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." @@ -12948,5 +12998,23 @@ }, "paymentMethodUpdateError": { "message": "There was an error updating your payment method." + }, + "sendPasswordInvalidAskOwner": { + "message": "Invalid password. Ask the sender for the password needed to access this Send.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "sendExpiresOn": { + "message": "This Send expires at $TIME$ on $DATE$", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated.", + "placeholders": { + "time": { + "content": "$1", + "example": "10:00 AM" + }, + "date": { + "content": "$2", + "example": "Jan 1, 1970" + } + } } } diff --git a/apps/web/src/locales/et/messages.json b/apps/web/src/locales/et/messages.json index e101d49d3cf..448103ed594 100644 --- a/apps/web/src/locales/et/messages.json +++ b/apps/web/src/locales/et/messages.json @@ -4337,6 +4337,15 @@ } } }, + "automaticallyConfirmedUserId": { + "message": "Automatically confirmed user $ID$.", + "placeholders": { + "id": { + "content": "$1", + "example": "John Smith" + } + } + }, "editedUserId": { "message": "Muutis kasutajat $ID$.", "placeholders": { @@ -4596,29 +4605,26 @@ "generatingYourAccessIntelligence": { "message": "Generating your Access Intelligence..." }, - "fetchingMemberData": { - "message": "Fetching member data..." - }, - "analyzingPasswordHealth": { - "message": "Analyzing password health..." - }, - "calculatingRiskScores": { - "message": "Calculating risk scores..." - }, - "generatingReportData": { - "message": "Generating report data..." - }, - "savingReport": { - "message": "Saving report..." - }, - "compilingInsights": { - "message": "Compiling insights..." - }, "loadingProgress": { "message": "Loading progress" }, - "thisMightTakeFewMinutes": { - "message": "This might take a few minutes." + "reviewingMemberData": { + "message": "Reviewing member data..." + }, + "analyzingPasswords": { + "message": "Analyzing passwords..." + }, + "calculatingRisks": { + "message": "Calculating risks..." + }, + "generatingReports": { + "message": "Generating reports..." + }, + "compilingInsightsProgress": { + "message": "Compiling insights..." + }, + "reportGenerationDone": { + "message": "Done!" }, "riskInsightsRunReport": { "message": "Run report" @@ -5849,10 +5855,6 @@ "message": "Sa ei tea parooli? Küsi seda konkreetse Sendi saatjalt.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, - "sendHiddenByDefault": { - "message": "See Send on vaikeseades peidetud. Saad selle nähtavust alloleva nupu abil seadistada.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, "downloadAttachments": { "message": "Lae manused alla" }, @@ -6145,6 +6147,21 @@ "autoConfirmCheckBoxLabel": { "message": "I accept these risks and policy updates" }, + "autoConfirmEnabledByAdmin": { + "message": "Turned on Automatic user confirmation setting" + }, + "autoConfirmDisabledByAdmin": { + "message": "Turned off Automatic user confirmation setting" + }, + "autoConfirmEnabledByPortal": { + "message": "Added Automatic user confirmation policy" + }, + "autoConfirmDisabledByPortal": { + "message": "Removed Automatic user confirmation policy" + }, + "system": { + "message": "System" + }, "personalOwnership": { "message": "Personaalne salvestamine" }, @@ -7400,6 +7417,9 @@ "invalidVerificationCode": { "message": "Vale kinnituskood" }, + "invalidEmailOrVerificationCode": { + "message": "Invalid email or verification code" + }, "keyConnectorDomain": { "message": "Key Connector domain" }, @@ -12857,6 +12877,21 @@ "storageFullDescription": { "message": "You have used all $GB$ GB of your encrypted storage. To continue storing files, add more storage." }, + "extensionPromptHeading": { + "message": "Get the extension for easy vault access" + }, + "extensionPromptBody": { + "message": "With the browser extension installed, you'll take Bitwarden everywhere online. It'll fill in passwords, so you can log into your accounts with a single click." + }, + "extensionPromptImageAlt": { + "message": "A web browser showing the Bitwarden extension with autofill items for the current webpage." + }, + "skip": { + "message": "Skip" + }, + "downloadExtension": { + "message": "Download extension" + }, "whoCanView": { "message": "Who can view" }, @@ -12869,6 +12904,9 @@ "enterMultipleEmailsSeparatedByComma": { "message": "Enter multiple emails by separating with a comma." }, + "emailsRequiredChangeAccessType": { + "message": "Email verification requires at least one email address. To remove all emails, change the access type above." + }, "emailPlaceholder": { "message": "user@bitwarden.com , user@acme.com" }, @@ -12893,6 +12931,18 @@ "invalidSendPassword": { "message": "Invalid Send password" }, + "vaultWelcomeDialogTitle": { + "message": "You're in! Welcome to Bitwarden" + }, + "vaultWelcomeDialogDescription": { + "message": "Store all your passwords and personal info in your Bitwarden vault. We'll show you around." + }, + "vaultWelcomeDialogPrimaryCta": { + "message": "Start tour" + }, + "vaultWelcomeDialogDismissCta": { + "message": "Skip" + }, "sendPasswordHelperText": { "message": "Individuals will need to enter the password to view this Send", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." @@ -12948,5 +12998,23 @@ }, "paymentMethodUpdateError": { "message": "There was an error updating your payment method." + }, + "sendPasswordInvalidAskOwner": { + "message": "Invalid password. Ask the sender for the password needed to access this Send.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "sendExpiresOn": { + "message": "This Send expires at $TIME$ on $DATE$", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated.", + "placeholders": { + "time": { + "content": "$1", + "example": "10:00 AM" + }, + "date": { + "content": "$2", + "example": "Jan 1, 1970" + } + } } } diff --git a/apps/web/src/locales/eu/messages.json b/apps/web/src/locales/eu/messages.json index 75f55a0d0f0..3376ef33f63 100644 --- a/apps/web/src/locales/eu/messages.json +++ b/apps/web/src/locales/eu/messages.json @@ -4337,6 +4337,15 @@ } } }, + "automaticallyConfirmedUserId": { + "message": "Automatically confirmed user $ID$.", + "placeholders": { + "id": { + "content": "$1", + "example": "John Smith" + } + } + }, "editedUserId": { "message": "$ID$ erabiltzailea editatua.", "placeholders": { @@ -4596,29 +4605,26 @@ "generatingYourAccessIntelligence": { "message": "Generating your Access Intelligence..." }, - "fetchingMemberData": { - "message": "Fetching member data..." - }, - "analyzingPasswordHealth": { - "message": "Analyzing password health..." - }, - "calculatingRiskScores": { - "message": "Calculating risk scores..." - }, - "generatingReportData": { - "message": "Generating report data..." - }, - "savingReport": { - "message": "Saving report..." - }, - "compilingInsights": { - "message": "Compiling insights..." - }, "loadingProgress": { "message": "Loading progress" }, - "thisMightTakeFewMinutes": { - "message": "This might take a few minutes." + "reviewingMemberData": { + "message": "Reviewing member data..." + }, + "analyzingPasswords": { + "message": "Analyzing passwords..." + }, + "calculatingRisks": { + "message": "Calculating risks..." + }, + "generatingReports": { + "message": "Generating reports..." + }, + "compilingInsightsProgress": { + "message": "Compiling insights..." + }, + "reportGenerationDone": { + "message": "Done!" }, "riskInsightsRunReport": { "message": "Run report" @@ -5849,10 +5855,6 @@ "message": "Ez duzu pasahitza ezagutzen? Eskatu bidaltzaileari Send honetara sartzeko behar den pasahitza.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, - "sendHiddenByDefault": { - "message": "Send hau modu lehenetsian ezkutatuta dago. Beheko botoia sakatuz alda dezakezu ikusgarritasuna.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, "downloadAttachments": { "message": "Download attachments" }, @@ -6145,6 +6147,21 @@ "autoConfirmCheckBoxLabel": { "message": "I accept these risks and policy updates" }, + "autoConfirmEnabledByAdmin": { + "message": "Turned on Automatic user confirmation setting" + }, + "autoConfirmDisabledByAdmin": { + "message": "Turned off Automatic user confirmation setting" + }, + "autoConfirmEnabledByPortal": { + "message": "Added Automatic user confirmation policy" + }, + "autoConfirmDisabledByPortal": { + "message": "Removed Automatic user confirmation policy" + }, + "system": { + "message": "System" + }, "personalOwnership": { "message": "Ezabatu kutxa gotor pertsonala" }, @@ -7400,6 +7417,9 @@ "invalidVerificationCode": { "message": "Egiaztatze-kodea ez da baliozkoa" }, + "invalidEmailOrVerificationCode": { + "message": "Invalid email or verification code" + }, "keyConnectorDomain": { "message": "Key Connector domain" }, @@ -12857,6 +12877,21 @@ "storageFullDescription": { "message": "You have used all $GB$ GB of your encrypted storage. To continue storing files, add more storage." }, + "extensionPromptHeading": { + "message": "Get the extension for easy vault access" + }, + "extensionPromptBody": { + "message": "With the browser extension installed, you'll take Bitwarden everywhere online. It'll fill in passwords, so you can log into your accounts with a single click." + }, + "extensionPromptImageAlt": { + "message": "A web browser showing the Bitwarden extension with autofill items for the current webpage." + }, + "skip": { + "message": "Skip" + }, + "downloadExtension": { + "message": "Download extension" + }, "whoCanView": { "message": "Who can view" }, @@ -12869,6 +12904,9 @@ "enterMultipleEmailsSeparatedByComma": { "message": "Enter multiple emails by separating with a comma." }, + "emailsRequiredChangeAccessType": { + "message": "Email verification requires at least one email address. To remove all emails, change the access type above." + }, "emailPlaceholder": { "message": "user@bitwarden.com , user@acme.com" }, @@ -12893,6 +12931,18 @@ "invalidSendPassword": { "message": "Invalid Send password" }, + "vaultWelcomeDialogTitle": { + "message": "You're in! Welcome to Bitwarden" + }, + "vaultWelcomeDialogDescription": { + "message": "Store all your passwords and personal info in your Bitwarden vault. We'll show you around." + }, + "vaultWelcomeDialogPrimaryCta": { + "message": "Start tour" + }, + "vaultWelcomeDialogDismissCta": { + "message": "Skip" + }, "sendPasswordHelperText": { "message": "Individuals will need to enter the password to view this Send", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." @@ -12948,5 +12998,23 @@ }, "paymentMethodUpdateError": { "message": "There was an error updating your payment method." + }, + "sendPasswordInvalidAskOwner": { + "message": "Invalid password. Ask the sender for the password needed to access this Send.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "sendExpiresOn": { + "message": "This Send expires at $TIME$ on $DATE$", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated.", + "placeholders": { + "time": { + "content": "$1", + "example": "10:00 AM" + }, + "date": { + "content": "$2", + "example": "Jan 1, 1970" + } + } } } diff --git a/apps/web/src/locales/fa/messages.json b/apps/web/src/locales/fa/messages.json index d1efb2a239f..25b2a61c256 100644 --- a/apps/web/src/locales/fa/messages.json +++ b/apps/web/src/locales/fa/messages.json @@ -4337,6 +4337,15 @@ } } }, + "automaticallyConfirmedUserId": { + "message": "Automatically confirmed user $ID$.", + "placeholders": { + "id": { + "content": "$1", + "example": "John Smith" + } + } + }, "editedUserId": { "message": "کاربر $ID$ ویرایش شد.", "placeholders": { @@ -4596,29 +4605,26 @@ "generatingYourAccessIntelligence": { "message": "Generating your Access Intelligence..." }, - "fetchingMemberData": { - "message": "Fetching member data..." - }, - "analyzingPasswordHealth": { - "message": "Analyzing password health..." - }, - "calculatingRiskScores": { - "message": "Calculating risk scores..." - }, - "generatingReportData": { - "message": "Generating report data..." - }, - "savingReport": { - "message": "Saving report..." - }, - "compilingInsights": { - "message": "Compiling insights..." - }, "loadingProgress": { "message": "Loading progress" }, - "thisMightTakeFewMinutes": { - "message": "This might take a few minutes." + "reviewingMemberData": { + "message": "Reviewing member data..." + }, + "analyzingPasswords": { + "message": "Analyzing passwords..." + }, + "calculatingRisks": { + "message": "Calculating risks..." + }, + "generatingReports": { + "message": "Generating reports..." + }, + "compilingInsightsProgress": { + "message": "Compiling insights..." + }, + "reportGenerationDone": { + "message": "Done!" }, "riskInsightsRunReport": { "message": "Run report" @@ -5849,10 +5855,6 @@ "message": "کلمه عبور را نمی‌دانید؟ از فرستنده کلمه عبور لازم را برای دسترسی به این ارسال بخواهید.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, - "sendHiddenByDefault": { - "message": "این ارسال به طور پیش‌فرض پنهان است. با استفاده از دکمه زیر می‌توانید نمایان بودن آن را تغییر دهید.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, "downloadAttachments": { "message": "بارگیری پیوست‌ها" }, @@ -6145,6 +6147,21 @@ "autoConfirmCheckBoxLabel": { "message": "I accept these risks and policy updates" }, + "autoConfirmEnabledByAdmin": { + "message": "Turned on Automatic user confirmation setting" + }, + "autoConfirmDisabledByAdmin": { + "message": "Turned off Automatic user confirmation setting" + }, + "autoConfirmEnabledByPortal": { + "message": "Added Automatic user confirmation policy" + }, + "autoConfirmDisabledByPortal": { + "message": "Removed Automatic user confirmation policy" + }, + "system": { + "message": "System" + }, "personalOwnership": { "message": "حذف گاوصندوق شخصی" }, @@ -7400,6 +7417,9 @@ "invalidVerificationCode": { "message": "کد تأیید نامعتبر است" }, + "invalidEmailOrVerificationCode": { + "message": "Invalid email or verification code" + }, "keyConnectorDomain": { "message": "دامنه رابط کلید" }, @@ -12857,6 +12877,21 @@ "storageFullDescription": { "message": "You have used all $GB$ GB of your encrypted storage. To continue storing files, add more storage." }, + "extensionPromptHeading": { + "message": "Get the extension for easy vault access" + }, + "extensionPromptBody": { + "message": "With the browser extension installed, you'll take Bitwarden everywhere online. It'll fill in passwords, so you can log into your accounts with a single click." + }, + "extensionPromptImageAlt": { + "message": "A web browser showing the Bitwarden extension with autofill items for the current webpage." + }, + "skip": { + "message": "Skip" + }, + "downloadExtension": { + "message": "Download extension" + }, "whoCanView": { "message": "Who can view" }, @@ -12869,6 +12904,9 @@ "enterMultipleEmailsSeparatedByComma": { "message": "Enter multiple emails by separating with a comma." }, + "emailsRequiredChangeAccessType": { + "message": "Email verification requires at least one email address. To remove all emails, change the access type above." + }, "emailPlaceholder": { "message": "user@bitwarden.com , user@acme.com" }, @@ -12893,6 +12931,18 @@ "invalidSendPassword": { "message": "Invalid Send password" }, + "vaultWelcomeDialogTitle": { + "message": "You're in! Welcome to Bitwarden" + }, + "vaultWelcomeDialogDescription": { + "message": "Store all your passwords and personal info in your Bitwarden vault. We'll show you around." + }, + "vaultWelcomeDialogPrimaryCta": { + "message": "Start tour" + }, + "vaultWelcomeDialogDismissCta": { + "message": "Skip" + }, "sendPasswordHelperText": { "message": "Individuals will need to enter the password to view this Send", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." @@ -12948,5 +12998,23 @@ }, "paymentMethodUpdateError": { "message": "There was an error updating your payment method." + }, + "sendPasswordInvalidAskOwner": { + "message": "Invalid password. Ask the sender for the password needed to access this Send.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "sendExpiresOn": { + "message": "This Send expires at $TIME$ on $DATE$", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated.", + "placeholders": { + "time": { + "content": "$1", + "example": "10:00 AM" + }, + "date": { + "content": "$2", + "example": "Jan 1, 1970" + } + } } } diff --git a/apps/web/src/locales/fi/messages.json b/apps/web/src/locales/fi/messages.json index 4c6b3ef62d8..f562a98c60c 100644 --- a/apps/web/src/locales/fi/messages.json +++ b/apps/web/src/locales/fi/messages.json @@ -4337,6 +4337,15 @@ } } }, + "automaticallyConfirmedUserId": { + "message": "Automatically confirmed user $ID$.", + "placeholders": { + "id": { + "content": "$1", + "example": "John Smith" + } + } + }, "editedUserId": { "message": "Muokkasi käyttäjää \"$ID$\".", "placeholders": { @@ -4596,29 +4605,26 @@ "generatingYourAccessIntelligence": { "message": "Generating your Access Intelligence..." }, - "fetchingMemberData": { - "message": "Fetching member data..." - }, - "analyzingPasswordHealth": { - "message": "Analyzing password health..." - }, - "calculatingRiskScores": { - "message": "Calculating risk scores..." - }, - "generatingReportData": { - "message": "Generating report data..." - }, - "savingReport": { - "message": "Saving report..." - }, - "compilingInsights": { - "message": "Compiling insights..." - }, "loadingProgress": { "message": "Loading progress" }, - "thisMightTakeFewMinutes": { - "message": "This might take a few minutes." + "reviewingMemberData": { + "message": "Reviewing member data..." + }, + "analyzingPasswords": { + "message": "Analyzing passwords..." + }, + "calculatingRisks": { + "message": "Calculating risks..." + }, + "generatingReports": { + "message": "Generating reports..." + }, + "compilingInsightsProgress": { + "message": "Compiling insights..." + }, + "reportGenerationDone": { + "message": "Done!" }, "riskInsightsRunReport": { "message": "Run report" @@ -5849,10 +5855,6 @@ "message": "Etkö tiedä salasanaa? Pyydä lähettäjältä tämän Sendin avaukseen tarvittavaa salasanaa.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, - "sendHiddenByDefault": { - "message": "Send on oletusarvoisesti piilotettu. Voit vaihtaa sen näkyvyyttä alla olevalla painikkeella.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, "downloadAttachments": { "message": "Lataa liitteet" }, @@ -6145,6 +6147,21 @@ "autoConfirmCheckBoxLabel": { "message": "I accept these risks and policy updates" }, + "autoConfirmEnabledByAdmin": { + "message": "Turned on Automatic user confirmation setting" + }, + "autoConfirmDisabledByAdmin": { + "message": "Turned off Automatic user confirmation setting" + }, + "autoConfirmEnabledByPortal": { + "message": "Added Automatic user confirmation policy" + }, + "autoConfirmDisabledByPortal": { + "message": "Removed Automatic user confirmation policy" + }, + "system": { + "message": "System" + }, "personalOwnership": { "message": "Poista yksityinen holvi" }, @@ -7400,6 +7417,9 @@ "invalidVerificationCode": { "message": "Virheellinen todennuskoodi" }, + "invalidEmailOrVerificationCode": { + "message": "Invalid email or verification code" + }, "keyConnectorDomain": { "message": "Key Connector domain" }, @@ -12857,6 +12877,21 @@ "storageFullDescription": { "message": "You have used all $GB$ GB of your encrypted storage. To continue storing files, add more storage." }, + "extensionPromptHeading": { + "message": "Get the extension for easy vault access" + }, + "extensionPromptBody": { + "message": "With the browser extension installed, you'll take Bitwarden everywhere online. It'll fill in passwords, so you can log into your accounts with a single click." + }, + "extensionPromptImageAlt": { + "message": "A web browser showing the Bitwarden extension with autofill items for the current webpage." + }, + "skip": { + "message": "Skip" + }, + "downloadExtension": { + "message": "Download extension" + }, "whoCanView": { "message": "Who can view" }, @@ -12869,6 +12904,9 @@ "enterMultipleEmailsSeparatedByComma": { "message": "Enter multiple emails by separating with a comma." }, + "emailsRequiredChangeAccessType": { + "message": "Email verification requires at least one email address. To remove all emails, change the access type above." + }, "emailPlaceholder": { "message": "user@bitwarden.com , user@acme.com" }, @@ -12893,6 +12931,18 @@ "invalidSendPassword": { "message": "Invalid Send password" }, + "vaultWelcomeDialogTitle": { + "message": "You're in! Welcome to Bitwarden" + }, + "vaultWelcomeDialogDescription": { + "message": "Store all your passwords and personal info in your Bitwarden vault. We'll show you around." + }, + "vaultWelcomeDialogPrimaryCta": { + "message": "Start tour" + }, + "vaultWelcomeDialogDismissCta": { + "message": "Skip" + }, "sendPasswordHelperText": { "message": "Individuals will need to enter the password to view this Send", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." @@ -12948,5 +12998,23 @@ }, "paymentMethodUpdateError": { "message": "There was an error updating your payment method." + }, + "sendPasswordInvalidAskOwner": { + "message": "Invalid password. Ask the sender for the password needed to access this Send.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "sendExpiresOn": { + "message": "This Send expires at $TIME$ on $DATE$", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated.", + "placeholders": { + "time": { + "content": "$1", + "example": "10:00 AM" + }, + "date": { + "content": "$2", + "example": "Jan 1, 1970" + } + } } } diff --git a/apps/web/src/locales/fil/messages.json b/apps/web/src/locales/fil/messages.json index e94c5b4fea6..af1a0105c83 100644 --- a/apps/web/src/locales/fil/messages.json +++ b/apps/web/src/locales/fil/messages.json @@ -4337,6 +4337,15 @@ } } }, + "automaticallyConfirmedUserId": { + "message": "Automatically confirmed user $ID$.", + "placeholders": { + "id": { + "content": "$1", + "example": "John Smith" + } + } + }, "editedUserId": { "message": "Na-edit ang user $ID$.", "placeholders": { @@ -4596,29 +4605,26 @@ "generatingYourAccessIntelligence": { "message": "Generating your Access Intelligence..." }, - "fetchingMemberData": { - "message": "Fetching member data..." - }, - "analyzingPasswordHealth": { - "message": "Analyzing password health..." - }, - "calculatingRiskScores": { - "message": "Calculating risk scores..." - }, - "generatingReportData": { - "message": "Generating report data..." - }, - "savingReport": { - "message": "Saving report..." - }, - "compilingInsights": { - "message": "Compiling insights..." - }, "loadingProgress": { "message": "Loading progress" }, - "thisMightTakeFewMinutes": { - "message": "This might take a few minutes." + "reviewingMemberData": { + "message": "Reviewing member data..." + }, + "analyzingPasswords": { + "message": "Analyzing passwords..." + }, + "calculatingRisks": { + "message": "Calculating risks..." + }, + "generatingReports": { + "message": "Generating reports..." + }, + "compilingInsightsProgress": { + "message": "Compiling insights..." + }, + "reportGenerationDone": { + "message": "Done!" }, "riskInsightsRunReport": { "message": "Run report" @@ -5849,10 +5855,6 @@ "message": "Hindi mo ba alam ang password Itanong sa nagpadala ang password na kailangan para ma-access ang Padala na ito.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, - "sendHiddenByDefault": { - "message": "Ang Send na ito ay nakatago bilang default. Maaari mong i toggle ang visibility nito gamit ang pindutan sa ibaba.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, "downloadAttachments": { "message": "Download attachments" }, @@ -6145,6 +6147,21 @@ "autoConfirmCheckBoxLabel": { "message": "I accept these risks and policy updates" }, + "autoConfirmEnabledByAdmin": { + "message": "Turned on Automatic user confirmation setting" + }, + "autoConfirmDisabledByAdmin": { + "message": "Turned off Automatic user confirmation setting" + }, + "autoConfirmEnabledByPortal": { + "message": "Added Automatic user confirmation policy" + }, + "autoConfirmDisabledByPortal": { + "message": "Removed Automatic user confirmation policy" + }, + "system": { + "message": "System" + }, "personalOwnership": { "message": "Alisin ang indibidwal na vault" }, @@ -7400,6 +7417,9 @@ "invalidVerificationCode": { "message": "Maling verification code" }, + "invalidEmailOrVerificationCode": { + "message": "Invalid email or verification code" + }, "keyConnectorDomain": { "message": "Key Connector domain" }, @@ -12857,6 +12877,21 @@ "storageFullDescription": { "message": "You have used all $GB$ GB of your encrypted storage. To continue storing files, add more storage." }, + "extensionPromptHeading": { + "message": "Get the extension for easy vault access" + }, + "extensionPromptBody": { + "message": "With the browser extension installed, you'll take Bitwarden everywhere online. It'll fill in passwords, so you can log into your accounts with a single click." + }, + "extensionPromptImageAlt": { + "message": "A web browser showing the Bitwarden extension with autofill items for the current webpage." + }, + "skip": { + "message": "Skip" + }, + "downloadExtension": { + "message": "Download extension" + }, "whoCanView": { "message": "Who can view" }, @@ -12869,6 +12904,9 @@ "enterMultipleEmailsSeparatedByComma": { "message": "Enter multiple emails by separating with a comma." }, + "emailsRequiredChangeAccessType": { + "message": "Email verification requires at least one email address. To remove all emails, change the access type above." + }, "emailPlaceholder": { "message": "user@bitwarden.com , user@acme.com" }, @@ -12893,6 +12931,18 @@ "invalidSendPassword": { "message": "Invalid Send password" }, + "vaultWelcomeDialogTitle": { + "message": "You're in! Welcome to Bitwarden" + }, + "vaultWelcomeDialogDescription": { + "message": "Store all your passwords and personal info in your Bitwarden vault. We'll show you around." + }, + "vaultWelcomeDialogPrimaryCta": { + "message": "Start tour" + }, + "vaultWelcomeDialogDismissCta": { + "message": "Skip" + }, "sendPasswordHelperText": { "message": "Individuals will need to enter the password to view this Send", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." @@ -12948,5 +12998,23 @@ }, "paymentMethodUpdateError": { "message": "There was an error updating your payment method." + }, + "sendPasswordInvalidAskOwner": { + "message": "Invalid password. Ask the sender for the password needed to access this Send.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "sendExpiresOn": { + "message": "This Send expires at $TIME$ on $DATE$", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated.", + "placeholders": { + "time": { + "content": "$1", + "example": "10:00 AM" + }, + "date": { + "content": "$2", + "example": "Jan 1, 1970" + } + } } } diff --git a/apps/web/src/locales/fr/messages.json b/apps/web/src/locales/fr/messages.json index e4183ff5692..65a62b5a12d 100644 --- a/apps/web/src/locales/fr/messages.json +++ b/apps/web/src/locales/fr/messages.json @@ -4337,6 +4337,15 @@ } } }, + "automaticallyConfirmedUserId": { + "message": "Utilisateur $ID$ automatiquement confirmé.", + "placeholders": { + "id": { + "content": "$1", + "example": "John Smith" + } + } + }, "editedUserId": { "message": "Utilisateur $ID$ modifié.", "placeholders": { @@ -4596,29 +4605,26 @@ "generatingYourAccessIntelligence": { "message": "Génération de votre Intelligence d'Accès..." }, - "fetchingMemberData": { - "message": "Récupération des données des membres..." - }, - "analyzingPasswordHealth": { - "message": "Analyse de la santé du mot de passe..." - }, - "calculatingRiskScores": { - "message": "Calcul des niveaux de risque..." - }, - "generatingReportData": { - "message": "Génération des données du rapport..." - }, - "savingReport": { - "message": "Enregistrement du rapport..." - }, - "compilingInsights": { - "message": "Compilation des aperçus..." - }, "loadingProgress": { "message": "Chargement de la progression" }, - "thisMightTakeFewMinutes": { - "message": "Cela peut prendre quelques minutes." + "reviewingMemberData": { + "message": "Révision des données du membre..." + }, + "analyzingPasswords": { + "message": "Analyse des mots de passe..." + }, + "calculatingRisks": { + "message": "Calcul des risques..." + }, + "generatingReports": { + "message": "Génération du rapport..." + }, + "compilingInsightsProgress": { + "message": "Compilation des observations..." + }, + "reportGenerationDone": { + "message": "Fini !" }, "riskInsightsRunReport": { "message": "Exécuter le rapport" @@ -5849,10 +5855,6 @@ "message": "Vous ne connaissez pas le mot de passe ? Demandez à l'expéditeur le mot de passe nécessaire pour accéder à ce Send.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, - "sendHiddenByDefault": { - "message": "Ce Send est masqué par défaut. Vous pouvez changer sa visibilité en utilisant le bouton ci-dessous.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, "downloadAttachments": { "message": "Télécharger les pièces jointes" }, @@ -6145,6 +6147,21 @@ "autoConfirmCheckBoxLabel": { "message": "J'accepte ces risques et mises à jour de la politique de sécurité" }, + "autoConfirmEnabledByAdmin": { + "message": "Activé le paramètre de confirmation automatique de l'utilisateur" + }, + "autoConfirmDisabledByAdmin": { + "message": "Désactivé le paramètre de confirmation automatique de l'utilisateur" + }, + "autoConfirmEnabledByPortal": { + "message": "Ajout de la politique de sécurité de confirmation automatique de l'utilisateur" + }, + "autoConfirmDisabledByPortal": { + "message": "Politique de sécurité de confirmation automatique de l'utilisateur retirée" + }, + "system": { + "message": "Système" + }, "personalOwnership": { "message": "Supprimer le coffre individuel" }, @@ -7400,6 +7417,9 @@ "invalidVerificationCode": { "message": "Code de vérification invalide" }, + "invalidEmailOrVerificationCode": { + "message": "Courriel ou code de vérification invalide" + }, "keyConnectorDomain": { "message": "Domaine Key Connector" }, @@ -12857,6 +12877,21 @@ "storageFullDescription": { "message": "Vous avez utilisé tous les $GB$ Go de votre stockage chiffré. Pour continuer à stocker des fichiers, ajoutez plus de stockage." }, + "extensionPromptHeading": { + "message": "Obtenir l'extension pour un accès facile au coffre" + }, + "extensionPromptBody": { + "message": "Avec l'extension de navigateur installée, vous emmènerez Bitwarden partout en ligne. Il remplira les mots de passe, faisant en sorte que vous puissiez vous connecter à vos comptes en un seul clic." + }, + "extensionPromptImageAlt": { + "message": "Un navigateur web montrant l'extension Bitwarden avec des éléments de saisie automatique pour la page web actuelle." + }, + "skip": { + "message": "Ignorer" + }, + "downloadExtension": { + "message": "Télécharger l'extension" + }, "whoCanView": { "message": "Qui peut afficher" }, @@ -12869,6 +12904,9 @@ "enterMultipleEmailsSeparatedByComma": { "message": "Entrez plusieurs courriels en les séparant avec une virgule." }, + "emailsRequiredChangeAccessType": { + "message": "La vérification de courriel requiert au moins une adresse courriel. Pour retirer tous les courriels, changez le type d'accès ci-dessus." + }, "emailPlaceholder": { "message": "user@bitwarden.com , user@acme.com" }, @@ -12893,6 +12931,18 @@ "invalidSendPassword": { "message": "Mot de passe Send invalide" }, + "vaultWelcomeDialogTitle": { + "message": "Vous y êtes! Bienvenue sur Bitwarden" + }, + "vaultWelcomeDialogDescription": { + "message": "Enregistrez tous vos mots de passe et vos informations personnelles dans votre coffre de Bitwarden. Nous vous ferons faire la visite." + }, + "vaultWelcomeDialogPrimaryCta": { + "message": "Commencer la visite" + }, + "vaultWelcomeDialogDismissCta": { + "message": "Ignorer" + }, "sendPasswordHelperText": { "message": "Les personnes devront entrer le mot de passe pour afficher ce Send", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." @@ -12948,5 +12998,23 @@ }, "paymentMethodUpdateError": { "message": "Une erreur s'est produite lors de la mise à jour de votre mode de paiement." + }, + "sendPasswordInvalidAskOwner": { + "message": "Mot de passe invalide. Demandez à l'expéditeur le mot de passe nécessaire pour accéder à ce Send.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "sendExpiresOn": { + "message": "Ce Send expire à $TIME$ le $DATE$", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated.", + "placeholders": { + "time": { + "content": "$1", + "example": "10:00 AM" + }, + "date": { + "content": "$2", + "example": "Jan 1, 1970" + } + } } } diff --git a/apps/web/src/locales/gl/messages.json b/apps/web/src/locales/gl/messages.json index de6c7782c6d..17426524033 100644 --- a/apps/web/src/locales/gl/messages.json +++ b/apps/web/src/locales/gl/messages.json @@ -4337,6 +4337,15 @@ } } }, + "automaticallyConfirmedUserId": { + "message": "Automatically confirmed user $ID$.", + "placeholders": { + "id": { + "content": "$1", + "example": "John Smith" + } + } + }, "editedUserId": { "message": "Edited user $ID$.", "placeholders": { @@ -4596,29 +4605,26 @@ "generatingYourAccessIntelligence": { "message": "Generating your Access Intelligence..." }, - "fetchingMemberData": { - "message": "Fetching member data..." - }, - "analyzingPasswordHealth": { - "message": "Analyzing password health..." - }, - "calculatingRiskScores": { - "message": "Calculating risk scores..." - }, - "generatingReportData": { - "message": "Generating report data..." - }, - "savingReport": { - "message": "Saving report..." - }, - "compilingInsights": { - "message": "Compiling insights..." - }, "loadingProgress": { "message": "Loading progress" }, - "thisMightTakeFewMinutes": { - "message": "This might take a few minutes." + "reviewingMemberData": { + "message": "Reviewing member data..." + }, + "analyzingPasswords": { + "message": "Analyzing passwords..." + }, + "calculatingRisks": { + "message": "Calculating risks..." + }, + "generatingReports": { + "message": "Generating reports..." + }, + "compilingInsightsProgress": { + "message": "Compiling insights..." + }, + "reportGenerationDone": { + "message": "Done!" }, "riskInsightsRunReport": { "message": "Run report" @@ -5849,10 +5855,6 @@ "message": "Don't know the password? Ask the sender for the password needed to access this Send.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, - "sendHiddenByDefault": { - "message": "This Send is hidden by default. You can toggle its visibility using the button below.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, "downloadAttachments": { "message": "Download attachments" }, @@ -6145,6 +6147,21 @@ "autoConfirmCheckBoxLabel": { "message": "I accept these risks and policy updates" }, + "autoConfirmEnabledByAdmin": { + "message": "Turned on Automatic user confirmation setting" + }, + "autoConfirmDisabledByAdmin": { + "message": "Turned off Automatic user confirmation setting" + }, + "autoConfirmEnabledByPortal": { + "message": "Added Automatic user confirmation policy" + }, + "autoConfirmDisabledByPortal": { + "message": "Removed Automatic user confirmation policy" + }, + "system": { + "message": "System" + }, "personalOwnership": { "message": "Remove individual vault" }, @@ -7400,6 +7417,9 @@ "invalidVerificationCode": { "message": "Invalid verification code" }, + "invalidEmailOrVerificationCode": { + "message": "Invalid email or verification code" + }, "keyConnectorDomain": { "message": "Key Connector domain" }, @@ -12857,6 +12877,21 @@ "storageFullDescription": { "message": "You have used all $GB$ GB of your encrypted storage. To continue storing files, add more storage." }, + "extensionPromptHeading": { + "message": "Get the extension for easy vault access" + }, + "extensionPromptBody": { + "message": "With the browser extension installed, you'll take Bitwarden everywhere online. It'll fill in passwords, so you can log into your accounts with a single click." + }, + "extensionPromptImageAlt": { + "message": "A web browser showing the Bitwarden extension with autofill items for the current webpage." + }, + "skip": { + "message": "Skip" + }, + "downloadExtension": { + "message": "Download extension" + }, "whoCanView": { "message": "Who can view" }, @@ -12869,6 +12904,9 @@ "enterMultipleEmailsSeparatedByComma": { "message": "Enter multiple emails by separating with a comma." }, + "emailsRequiredChangeAccessType": { + "message": "Email verification requires at least one email address. To remove all emails, change the access type above." + }, "emailPlaceholder": { "message": "user@bitwarden.com , user@acme.com" }, @@ -12893,6 +12931,18 @@ "invalidSendPassword": { "message": "Invalid Send password" }, + "vaultWelcomeDialogTitle": { + "message": "You're in! Welcome to Bitwarden" + }, + "vaultWelcomeDialogDescription": { + "message": "Store all your passwords and personal info in your Bitwarden vault. We'll show you around." + }, + "vaultWelcomeDialogPrimaryCta": { + "message": "Start tour" + }, + "vaultWelcomeDialogDismissCta": { + "message": "Skip" + }, "sendPasswordHelperText": { "message": "Individuals will need to enter the password to view this Send", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." @@ -12948,5 +12998,23 @@ }, "paymentMethodUpdateError": { "message": "There was an error updating your payment method." + }, + "sendPasswordInvalidAskOwner": { + "message": "Invalid password. Ask the sender for the password needed to access this Send.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "sendExpiresOn": { + "message": "This Send expires at $TIME$ on $DATE$", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated.", + "placeholders": { + "time": { + "content": "$1", + "example": "10:00 AM" + }, + "date": { + "content": "$2", + "example": "Jan 1, 1970" + } + } } } diff --git a/apps/web/src/locales/he/messages.json b/apps/web/src/locales/he/messages.json index 8829ed90e65..7685aaae5a1 100644 --- a/apps/web/src/locales/he/messages.json +++ b/apps/web/src/locales/he/messages.json @@ -4337,6 +4337,15 @@ } } }, + "automaticallyConfirmedUserId": { + "message": "Automatically confirmed user $ID$.", + "placeholders": { + "id": { + "content": "$1", + "example": "John Smith" + } + } + }, "editedUserId": { "message": "משתמש שנערך $ID$.", "placeholders": { @@ -4596,29 +4605,26 @@ "generatingYourAccessIntelligence": { "message": "Generating your Access Intelligence..." }, - "fetchingMemberData": { - "message": "Fetching member data..." - }, - "analyzingPasswordHealth": { - "message": "Analyzing password health..." - }, - "calculatingRiskScores": { - "message": "Calculating risk scores..." - }, - "generatingReportData": { - "message": "Generating report data..." - }, - "savingReport": { - "message": "Saving report..." - }, - "compilingInsights": { - "message": "Compiling insights..." - }, "loadingProgress": { "message": "Loading progress" }, - "thisMightTakeFewMinutes": { - "message": "This might take a few minutes." + "reviewingMemberData": { + "message": "Reviewing member data..." + }, + "analyzingPasswords": { + "message": "Analyzing passwords..." + }, + "calculatingRisks": { + "message": "Calculating risks..." + }, + "generatingReports": { + "message": "Generating reports..." + }, + "compilingInsightsProgress": { + "message": "Compiling insights..." + }, + "reportGenerationDone": { + "message": "Done!" }, "riskInsightsRunReport": { "message": "הרץ דוח" @@ -5849,10 +5855,6 @@ "message": "לא יודע את הסיסמה? בקש מהשולח את הסיסמה הדרושה עבור סֵנְד זה.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, - "sendHiddenByDefault": { - "message": "סֵנְד זה מוסתר כברירת מחדל. אתה יכול לשנות את מצב הנראות שלו באמצעות הלחצן למטה.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, "downloadAttachments": { "message": "הורד צרופות" }, @@ -6145,6 +6147,21 @@ "autoConfirmCheckBoxLabel": { "message": "אני מסכים לסיכונים ועדכוני מדיניות אלה" }, + "autoConfirmEnabledByAdmin": { + "message": "Turned on Automatic user confirmation setting" + }, + "autoConfirmDisabledByAdmin": { + "message": "Turned off Automatic user confirmation setting" + }, + "autoConfirmEnabledByPortal": { + "message": "Added Automatic user confirmation policy" + }, + "autoConfirmDisabledByPortal": { + "message": "Removed Automatic user confirmation policy" + }, + "system": { + "message": "System" + }, "personalOwnership": { "message": "הסר כספת אישית" }, @@ -7400,6 +7417,9 @@ "invalidVerificationCode": { "message": "קוד אימות שגוי" }, + "invalidEmailOrVerificationCode": { + "message": "Invalid email or verification code" + }, "keyConnectorDomain": { "message": "דומיין של Key Connector" }, @@ -12857,6 +12877,21 @@ "storageFullDescription": { "message": "You have used all $GB$ GB of your encrypted storage. To continue storing files, add more storage." }, + "extensionPromptHeading": { + "message": "Get the extension for easy vault access" + }, + "extensionPromptBody": { + "message": "With the browser extension installed, you'll take Bitwarden everywhere online. It'll fill in passwords, so you can log into your accounts with a single click." + }, + "extensionPromptImageAlt": { + "message": "A web browser showing the Bitwarden extension with autofill items for the current webpage." + }, + "skip": { + "message": "Skip" + }, + "downloadExtension": { + "message": "Download extension" + }, "whoCanView": { "message": "Who can view" }, @@ -12869,6 +12904,9 @@ "enterMultipleEmailsSeparatedByComma": { "message": "Enter multiple emails by separating with a comma." }, + "emailsRequiredChangeAccessType": { + "message": "Email verification requires at least one email address. To remove all emails, change the access type above." + }, "emailPlaceholder": { "message": "user@bitwarden.com , user@acme.com" }, @@ -12893,6 +12931,18 @@ "invalidSendPassword": { "message": "Invalid Send password" }, + "vaultWelcomeDialogTitle": { + "message": "You're in! Welcome to Bitwarden" + }, + "vaultWelcomeDialogDescription": { + "message": "Store all your passwords and personal info in your Bitwarden vault. We'll show you around." + }, + "vaultWelcomeDialogPrimaryCta": { + "message": "Start tour" + }, + "vaultWelcomeDialogDismissCta": { + "message": "Skip" + }, "sendPasswordHelperText": { "message": "Individuals will need to enter the password to view this Send", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." @@ -12948,5 +12998,23 @@ }, "paymentMethodUpdateError": { "message": "There was an error updating your payment method." + }, + "sendPasswordInvalidAskOwner": { + "message": "Invalid password. Ask the sender for the password needed to access this Send.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "sendExpiresOn": { + "message": "This Send expires at $TIME$ on $DATE$", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated.", + "placeholders": { + "time": { + "content": "$1", + "example": "10:00 AM" + }, + "date": { + "content": "$2", + "example": "Jan 1, 1970" + } + } } } diff --git a/apps/web/src/locales/hi/messages.json b/apps/web/src/locales/hi/messages.json index 41d32fb9587..1fad1304650 100644 --- a/apps/web/src/locales/hi/messages.json +++ b/apps/web/src/locales/hi/messages.json @@ -4337,6 +4337,15 @@ } } }, + "automaticallyConfirmedUserId": { + "message": "Automatically confirmed user $ID$.", + "placeholders": { + "id": { + "content": "$1", + "example": "John Smith" + } + } + }, "editedUserId": { "message": "Edited user $ID$.", "placeholders": { @@ -4596,29 +4605,26 @@ "generatingYourAccessIntelligence": { "message": "Generating your Access Intelligence..." }, - "fetchingMemberData": { - "message": "Fetching member data..." - }, - "analyzingPasswordHealth": { - "message": "Analyzing password health..." - }, - "calculatingRiskScores": { - "message": "Calculating risk scores..." - }, - "generatingReportData": { - "message": "Generating report data..." - }, - "savingReport": { - "message": "Saving report..." - }, - "compilingInsights": { - "message": "Compiling insights..." - }, "loadingProgress": { "message": "Loading progress" }, - "thisMightTakeFewMinutes": { - "message": "This might take a few minutes." + "reviewingMemberData": { + "message": "Reviewing member data..." + }, + "analyzingPasswords": { + "message": "Analyzing passwords..." + }, + "calculatingRisks": { + "message": "Calculating risks..." + }, + "generatingReports": { + "message": "Generating reports..." + }, + "compilingInsightsProgress": { + "message": "Compiling insights..." + }, + "reportGenerationDone": { + "message": "Done!" }, "riskInsightsRunReport": { "message": "Run report" @@ -5849,10 +5855,6 @@ "message": "Don't know the password? Ask the sender for the password needed to access this Send.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, - "sendHiddenByDefault": { - "message": "This Send is hidden by default. You can toggle its visibility using the button below.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, "downloadAttachments": { "message": "Download attachments" }, @@ -6145,6 +6147,21 @@ "autoConfirmCheckBoxLabel": { "message": "I accept these risks and policy updates" }, + "autoConfirmEnabledByAdmin": { + "message": "Turned on Automatic user confirmation setting" + }, + "autoConfirmDisabledByAdmin": { + "message": "Turned off Automatic user confirmation setting" + }, + "autoConfirmEnabledByPortal": { + "message": "Added Automatic user confirmation policy" + }, + "autoConfirmDisabledByPortal": { + "message": "Removed Automatic user confirmation policy" + }, + "system": { + "message": "System" + }, "personalOwnership": { "message": "Remove individual vault" }, @@ -7400,6 +7417,9 @@ "invalidVerificationCode": { "message": "Invalid verification code" }, + "invalidEmailOrVerificationCode": { + "message": "Invalid email or verification code" + }, "keyConnectorDomain": { "message": "Key Connector domain" }, @@ -12857,6 +12877,21 @@ "storageFullDescription": { "message": "You have used all $GB$ GB of your encrypted storage. To continue storing files, add more storage." }, + "extensionPromptHeading": { + "message": "Get the extension for easy vault access" + }, + "extensionPromptBody": { + "message": "With the browser extension installed, you'll take Bitwarden everywhere online. It'll fill in passwords, so you can log into your accounts with a single click." + }, + "extensionPromptImageAlt": { + "message": "A web browser showing the Bitwarden extension with autofill items for the current webpage." + }, + "skip": { + "message": "Skip" + }, + "downloadExtension": { + "message": "Download extension" + }, "whoCanView": { "message": "Who can view" }, @@ -12869,6 +12904,9 @@ "enterMultipleEmailsSeparatedByComma": { "message": "Enter multiple emails by separating with a comma." }, + "emailsRequiredChangeAccessType": { + "message": "Email verification requires at least one email address. To remove all emails, change the access type above." + }, "emailPlaceholder": { "message": "user@bitwarden.com , user@acme.com" }, @@ -12893,6 +12931,18 @@ "invalidSendPassword": { "message": "Invalid Send password" }, + "vaultWelcomeDialogTitle": { + "message": "You're in! Welcome to Bitwarden" + }, + "vaultWelcomeDialogDescription": { + "message": "Store all your passwords and personal info in your Bitwarden vault. We'll show you around." + }, + "vaultWelcomeDialogPrimaryCta": { + "message": "Start tour" + }, + "vaultWelcomeDialogDismissCta": { + "message": "Skip" + }, "sendPasswordHelperText": { "message": "Individuals will need to enter the password to view this Send", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." @@ -12948,5 +12998,23 @@ }, "paymentMethodUpdateError": { "message": "There was an error updating your payment method." + }, + "sendPasswordInvalidAskOwner": { + "message": "Invalid password. Ask the sender for the password needed to access this Send.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "sendExpiresOn": { + "message": "This Send expires at $TIME$ on $DATE$", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated.", + "placeholders": { + "time": { + "content": "$1", + "example": "10:00 AM" + }, + "date": { + "content": "$2", + "example": "Jan 1, 1970" + } + } } } diff --git a/apps/web/src/locales/hr/messages.json b/apps/web/src/locales/hr/messages.json index 41eac503c59..c67344da799 100644 --- a/apps/web/src/locales/hr/messages.json +++ b/apps/web/src/locales/hr/messages.json @@ -4337,6 +4337,15 @@ } } }, + "automaticallyConfirmedUserId": { + "message": "Automatically confirmed user $ID$.", + "placeholders": { + "id": { + "content": "$1", + "example": "John Smith" + } + } + }, "editedUserId": { "message": "Uređen korisnik $ID$.", "placeholders": { @@ -4596,29 +4605,26 @@ "generatingYourAccessIntelligence": { "message": "Generiranje tvoje pristupne inteligencije..." }, - "fetchingMemberData": { - "message": "Dohvaćanje podataka o članu…" - }, - "analyzingPasswordHealth": { - "message": "Analiziranje zdravlja lozinke…" - }, - "calculatingRiskScores": { - "message": "Izračun ocjene rizika…" - }, - "generatingReportData": { - "message": "Generiranje izvješća…" - }, - "savingReport": { - "message": "Spremanje izvještaja…" - }, - "compilingInsights": { - "message": "Sastavljanje uvida…" - }, "loadingProgress": { "message": "Učitavanje napretka" }, - "thisMightTakeFewMinutes": { - "message": "Ovo može potrajati nekoliko minuta." + "reviewingMemberData": { + "message": "Reviewing member data..." + }, + "analyzingPasswords": { + "message": "Analyzing passwords..." + }, + "calculatingRisks": { + "message": "Calculating risks..." + }, + "generatingReports": { + "message": "Generating reports..." + }, + "compilingInsightsProgress": { + "message": "Compiling insights..." + }, + "reportGenerationDone": { + "message": "Done!" }, "riskInsightsRunReport": { "message": "Pokreni izvješće" @@ -5849,10 +5855,6 @@ "message": "Ne znaš lozinku? Upitaj pošiljatelja za lozinku za pristup ovom Sendu.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, - "sendHiddenByDefault": { - "message": "Ovaj je Send zadano skriven. Moguće mu je promijeniti vidljivost.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, "downloadAttachments": { "message": "Preuzmi privitke" }, @@ -6145,6 +6147,21 @@ "autoConfirmCheckBoxLabel": { "message": "Prihavaćam ove rizike i ažurirana pravila" }, + "autoConfirmEnabledByAdmin": { + "message": "Turned on Automatic user confirmation setting" + }, + "autoConfirmDisabledByAdmin": { + "message": "Turned off Automatic user confirmation setting" + }, + "autoConfirmEnabledByPortal": { + "message": "Added Automatic user confirmation policy" + }, + "autoConfirmDisabledByPortal": { + "message": "Removed Automatic user confirmation policy" + }, + "system": { + "message": "System" + }, "personalOwnership": { "message": "Ukloni osobni trezor" }, @@ -7400,6 +7417,9 @@ "invalidVerificationCode": { "message": "Nevažeći kôd za provjeru" }, + "invalidEmailOrVerificationCode": { + "message": "Invalid email or verification code" + }, "keyConnectorDomain": { "message": "Domena konektora ključa" }, @@ -12857,6 +12877,21 @@ "storageFullDescription": { "message": "You have used all $GB$ GB of your encrypted storage. To continue storing files, add more storage." }, + "extensionPromptHeading": { + "message": "Get the extension for easy vault access" + }, + "extensionPromptBody": { + "message": "With the browser extension installed, you'll take Bitwarden everywhere online. It'll fill in passwords, so you can log into your accounts with a single click." + }, + "extensionPromptImageAlt": { + "message": "A web browser showing the Bitwarden extension with autofill items for the current webpage." + }, + "skip": { + "message": "Skip" + }, + "downloadExtension": { + "message": "Download extension" + }, "whoCanView": { "message": "Who can view" }, @@ -12869,6 +12904,9 @@ "enterMultipleEmailsSeparatedByComma": { "message": "Enter multiple emails by separating with a comma." }, + "emailsRequiredChangeAccessType": { + "message": "Email verification requires at least one email address. To remove all emails, change the access type above." + }, "emailPlaceholder": { "message": "user@bitwarden.com , user@acme.com" }, @@ -12893,6 +12931,18 @@ "invalidSendPassword": { "message": "Invalid Send password" }, + "vaultWelcomeDialogTitle": { + "message": "You're in! Welcome to Bitwarden" + }, + "vaultWelcomeDialogDescription": { + "message": "Store all your passwords and personal info in your Bitwarden vault. We'll show you around." + }, + "vaultWelcomeDialogPrimaryCta": { + "message": "Start tour" + }, + "vaultWelcomeDialogDismissCta": { + "message": "Skip" + }, "sendPasswordHelperText": { "message": "Individuals will need to enter the password to view this Send", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." @@ -12948,5 +12998,23 @@ }, "paymentMethodUpdateError": { "message": "There was an error updating your payment method." + }, + "sendPasswordInvalidAskOwner": { + "message": "Invalid password. Ask the sender for the password needed to access this Send.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "sendExpiresOn": { + "message": "This Send expires at $TIME$ on $DATE$", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated.", + "placeholders": { + "time": { + "content": "$1", + "example": "10:00 AM" + }, + "date": { + "content": "$2", + "example": "Jan 1, 1970" + } + } } } diff --git a/apps/web/src/locales/hu/messages.json b/apps/web/src/locales/hu/messages.json index 9b8a98e5625..ed38bd4b0e7 100644 --- a/apps/web/src/locales/hu/messages.json +++ b/apps/web/src/locales/hu/messages.json @@ -4337,6 +4337,15 @@ } } }, + "automaticallyConfirmedUserId": { + "message": "$ID$ felhasználó automatikusan megerősítésre került.", + "placeholders": { + "id": { + "content": "$1", + "example": "John Smith" + } + } + }, "editedUserId": { "message": "$ID$ azonosítójú felhasználó módosításra került.", "placeholders": { @@ -4596,29 +4605,26 @@ "generatingYourAccessIntelligence": { "message": "Hozzáférési intelligencia generálása..." }, - "fetchingMemberData": { - "message": "Tagi adatok lekérése..." - }, - "analyzingPasswordHealth": { - "message": "A jelszó állapot elemzése..." - }, - "calculatingRiskScores": { - "message": "Kockázati pontszámok kiszámítása..." - }, - "generatingReportData": { - "message": "Jelentés adatok generálása..." - }, - "savingReport": { - "message": "Jelentés mentése..." - }, - "compilingInsights": { - "message": "Betekintések összeállítása..." - }, "loadingProgress": { "message": "Feldolgozás betöltése" }, - "thisMightTakeFewMinutes": { - "message": "Ez eltarthat pár percig." + "reviewingMemberData": { + "message": "Tagi adatok lekérése..." + }, + "analyzingPasswords": { + "message": "A jelszavak elemzése..." + }, + "calculatingRisks": { + "message": "A kockázatok kiszámítása..." + }, + "generatingReports": { + "message": "Jelentések generálása..." + }, + "compilingInsightsProgress": { + "message": "Betekintések összeállítása..." + }, + "reportGenerationDone": { + "message": "Kész!" }, "riskInsightsRunReport": { "message": "Jelentés futtatása" @@ -5849,10 +5855,6 @@ "message": "Nem ismerjük a jelszót? Kérdezzünk rá a küldőnél a Send elérésére szükséges jelszóért.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, - "sendHiddenByDefault": { - "message": "Ez a Send alapértelmezésben rejtett. Az alábbi gombbal átváltható a láthatósága.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, "downloadAttachments": { "message": "Mellékletek letöltése" }, @@ -6145,6 +6147,21 @@ "autoConfirmCheckBoxLabel": { "message": "Elfogadom ezeket a kockázatokat és a szabályzat frissítéseit." }, + "autoConfirmEnabledByAdmin": { + "message": "Az automatikus felhasználó megerősítés beállítás bekapcsolásra került." + }, + "autoConfirmDisabledByAdmin": { + "message": "Az automatikus felhasználó megerősítés beállítás kikapcsolásra került." + }, + "autoConfirmEnabledByPortal": { + "message": "Az automatikus felhasználó megerősítés rendszabály hozzáadásra került." + }, + "autoConfirmDisabledByPortal": { + "message": "Az automatikus felhasználó megerősítés rendszabály eltávolításra került." + }, + "system": { + "message": "Rendszer" + }, "personalOwnership": { "message": "Személyes tulajdon" }, @@ -7400,6 +7417,9 @@ "invalidVerificationCode": { "message": "Érvénytelen ellenőrző kód" }, + "invalidEmailOrVerificationCode": { + "message": "Az email cím vagy az ellenőrző kód érvénytelen." + }, "keyConnectorDomain": { "message": "Key Connector tartomány" }, @@ -12857,6 +12877,21 @@ "storageFullDescription": { "message": "A titkosított tárhely összes $GB$ mérete felhasználásra került. A fájlok tárolásának folytatásához adjunk hozzá további tárhelyet." }, + "extensionPromptHeading": { + "message": "Szerezzük be a kiterjesztést a széf könnyű eléréséhez." + }, + "extensionPromptBody": { + "message": "A böngésző kiterjesztés telepítésével a Bitwardent mindenhová magunkkal vihetjük. Kitölti a jelszavakat, így egyetlen kattintással bejelentkezhetünk a fiókjainkba." + }, + "extensionPromptImageAlt": { + "message": "Egy webböngésző, amely a Bitwarden kiterjesztést jeleníti meg az aktuális weboldal automatikus kitöltési elemeivel." + }, + "skip": { + "message": "Kihagyás" + }, + "downloadExtension": { + "message": "Kiterjesztés letöltése" + }, "whoCanView": { "message": "Ki láthatja" }, @@ -12869,6 +12904,9 @@ "enterMultipleEmailsSeparatedByComma": { "message": "Írjunk be több email címet vesszővel elválasztva." }, + "emailsRequiredChangeAccessType": { + "message": "Az email cím ellenőrzéshez legalább egy email cím szükséges. Az összes email cím eltávolításához módosítsuk a fenti hozzáférési típust." + }, "emailPlaceholder": { "message": "user@bitwarden.com , user@acme.com" }, @@ -12893,6 +12931,18 @@ "invalidSendPassword": { "message": "Érvénytelen a Send jelszó." }, + "vaultWelcomeDialogTitle": { + "message": "Megérkeztünk! Üdvözlet a Bitwardenben" + }, + "vaultWelcomeDialogDescription": { + "message": "Az összes jelszó és személyes adat tárolása a Bitwarden trezorban. Nézzünk körbe." + }, + "vaultWelcomeDialogPrimaryCta": { + "message": "Túra elkezdése" + }, + "vaultWelcomeDialogDismissCta": { + "message": "Kihagyás" + }, "sendPasswordHelperText": { "message": "A személyeknek meg kell adniuk a jelszót a Send elem megtekintéséhez.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." @@ -12948,5 +12998,23 @@ }, "paymentMethodUpdateError": { "message": "Hiba történt a fizetési mód frissítésekor." + }, + "sendPasswordInvalidAskOwner": { + "message": "A jelszó érvénytelen. Kérjük el a feladótól a Send elem eléréséhez szükséges jelszót.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "sendExpiresOn": { + "message": "A Send elem lejár: $DATE$ - $TIME$", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated.", + "placeholders": { + "time": { + "content": "$1", + "example": "10:00 AM" + }, + "date": { + "content": "$2", + "example": "Jan 1, 1970" + } + } } } diff --git a/apps/web/src/locales/id/messages.json b/apps/web/src/locales/id/messages.json index 30746054e41..c1967096299 100644 --- a/apps/web/src/locales/id/messages.json +++ b/apps/web/src/locales/id/messages.json @@ -4337,6 +4337,15 @@ } } }, + "automaticallyConfirmedUserId": { + "message": "Automatically confirmed user $ID$.", + "placeholders": { + "id": { + "content": "$1", + "example": "John Smith" + } + } + }, "editedUserId": { "message": "$ID$ telah diedit.", "placeholders": { @@ -4596,29 +4605,26 @@ "generatingYourAccessIntelligence": { "message": "Generating your Access Intelligence..." }, - "fetchingMemberData": { - "message": "Fetching member data..." - }, - "analyzingPasswordHealth": { - "message": "Analyzing password health..." - }, - "calculatingRiskScores": { - "message": "Calculating risk scores..." - }, - "generatingReportData": { - "message": "Generating report data..." - }, - "savingReport": { - "message": "Saving report..." - }, - "compilingInsights": { - "message": "Compiling insights..." - }, "loadingProgress": { "message": "Loading progress" }, - "thisMightTakeFewMinutes": { - "message": "This might take a few minutes." + "reviewingMemberData": { + "message": "Reviewing member data..." + }, + "analyzingPasswords": { + "message": "Analyzing passwords..." + }, + "calculatingRisks": { + "message": "Calculating risks..." + }, + "generatingReports": { + "message": "Generating reports..." + }, + "compilingInsightsProgress": { + "message": "Compiling insights..." + }, + "reportGenerationDone": { + "message": "Done!" }, "riskInsightsRunReport": { "message": "Jalankan laporan" @@ -5849,10 +5855,6 @@ "message": "Tidak tahu sandinya? Tanyakan pengirim untuk sandi yang diperlukan untuk mengakses Kirim ini.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, - "sendHiddenByDefault": { - "message": "Pengiriman ini disembunyikan secara default. Anda dapat mengubah visibilitasnya menggunakan tombol di bawah ini.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, "downloadAttachments": { "message": "Unduh lampiran" }, @@ -6145,6 +6147,21 @@ "autoConfirmCheckBoxLabel": { "message": "Saya menerima risiko dan pembaruan kebijakan ini" }, + "autoConfirmEnabledByAdmin": { + "message": "Turned on Automatic user confirmation setting" + }, + "autoConfirmDisabledByAdmin": { + "message": "Turned off Automatic user confirmation setting" + }, + "autoConfirmEnabledByPortal": { + "message": "Added Automatic user confirmation policy" + }, + "autoConfirmDisabledByPortal": { + "message": "Removed Automatic user confirmation policy" + }, + "system": { + "message": "System" + }, "personalOwnership": { "message": "Kepemilikan Pribadi" }, @@ -7400,6 +7417,9 @@ "invalidVerificationCode": { "message": "Kode verifikasi tidak valid" }, + "invalidEmailOrVerificationCode": { + "message": "Invalid email or verification code" + }, "keyConnectorDomain": { "message": "Key Connector domain" }, @@ -12857,6 +12877,21 @@ "storageFullDescription": { "message": "You have used all $GB$ GB of your encrypted storage. To continue storing files, add more storage." }, + "extensionPromptHeading": { + "message": "Get the extension for easy vault access" + }, + "extensionPromptBody": { + "message": "With the browser extension installed, you'll take Bitwarden everywhere online. It'll fill in passwords, so you can log into your accounts with a single click." + }, + "extensionPromptImageAlt": { + "message": "A web browser showing the Bitwarden extension with autofill items for the current webpage." + }, + "skip": { + "message": "Skip" + }, + "downloadExtension": { + "message": "Download extension" + }, "whoCanView": { "message": "Who can view" }, @@ -12869,6 +12904,9 @@ "enterMultipleEmailsSeparatedByComma": { "message": "Enter multiple emails by separating with a comma." }, + "emailsRequiredChangeAccessType": { + "message": "Email verification requires at least one email address. To remove all emails, change the access type above." + }, "emailPlaceholder": { "message": "user@bitwarden.com , user@acme.com" }, @@ -12893,6 +12931,18 @@ "invalidSendPassword": { "message": "Invalid Send password" }, + "vaultWelcomeDialogTitle": { + "message": "You're in! Welcome to Bitwarden" + }, + "vaultWelcomeDialogDescription": { + "message": "Store all your passwords and personal info in your Bitwarden vault. We'll show you around." + }, + "vaultWelcomeDialogPrimaryCta": { + "message": "Start tour" + }, + "vaultWelcomeDialogDismissCta": { + "message": "Skip" + }, "sendPasswordHelperText": { "message": "Individuals will need to enter the password to view this Send", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." @@ -12948,5 +12998,23 @@ }, "paymentMethodUpdateError": { "message": "There was an error updating your payment method." + }, + "sendPasswordInvalidAskOwner": { + "message": "Invalid password. Ask the sender for the password needed to access this Send.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "sendExpiresOn": { + "message": "This Send expires at $TIME$ on $DATE$", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated.", + "placeholders": { + "time": { + "content": "$1", + "example": "10:00 AM" + }, + "date": { + "content": "$2", + "example": "Jan 1, 1970" + } + } } } diff --git a/apps/web/src/locales/it/messages.json b/apps/web/src/locales/it/messages.json index 1e89c1624f6..ac04390751d 100644 --- a/apps/web/src/locales/it/messages.json +++ b/apps/web/src/locales/it/messages.json @@ -4337,6 +4337,15 @@ } } }, + "automaticallyConfirmedUserId": { + "message": "Automatically confirmed user $ID$.", + "placeholders": { + "id": { + "content": "$1", + "example": "John Smith" + } + } + }, "editedUserId": { "message": "Utente $ID$ modificato.", "placeholders": { @@ -4596,29 +4605,26 @@ "generatingYourAccessIntelligence": { "message": "Generazione del tuo Access Intelligence..." }, - "fetchingMemberData": { - "message": "Recupero dei dati dei membri..." - }, - "analyzingPasswordHealth": { - "message": "Analisi della salute della password..." - }, - "calculatingRiskScores": { - "message": "Calcolo dei punteggi di rischio..." - }, - "generatingReportData": { - "message": "Generazione dati del rapporto..." - }, - "savingReport": { - "message": "Salvataggio..." - }, - "compilingInsights": { - "message": "Compilazione dei dati..." - }, "loadingProgress": { "message": "Caricamento in corso" }, - "thisMightTakeFewMinutes": { - "message": "Attendi..." + "reviewingMemberData": { + "message": "Reviewing member data..." + }, + "analyzingPasswords": { + "message": "Analyzing passwords..." + }, + "calculatingRisks": { + "message": "Calculating risks..." + }, + "generatingReports": { + "message": "Generating reports..." + }, + "compilingInsightsProgress": { + "message": "Compiling insights..." + }, + "reportGenerationDone": { + "message": "Done!" }, "riskInsightsRunReport": { "message": "Avvia report" @@ -5849,10 +5855,6 @@ "message": "Non conosci la password? Chiedi al mittente la password necessaria per accesso a questo Send.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, - "sendHiddenByDefault": { - "message": "Questo Send è nascosto per impostazione predefinita. Modifica la sua visibilità usando questo pulsante.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, "downloadAttachments": { "message": "Scarica allegati" }, @@ -6145,6 +6147,21 @@ "autoConfirmCheckBoxLabel": { "message": "Accetto questi rischi e aggiornamenti sulle politiche" }, + "autoConfirmEnabledByAdmin": { + "message": "Turned on Automatic user confirmation setting" + }, + "autoConfirmDisabledByAdmin": { + "message": "Turned off Automatic user confirmation setting" + }, + "autoConfirmEnabledByPortal": { + "message": "Added Automatic user confirmation policy" + }, + "autoConfirmDisabledByPortal": { + "message": "Removed Automatic user confirmation policy" + }, + "system": { + "message": "System" + }, "personalOwnership": { "message": "Rimuovi cassaforte individuale" }, @@ -7400,6 +7417,9 @@ "invalidVerificationCode": { "message": "Codice di verifica non valido" }, + "invalidEmailOrVerificationCode": { + "message": "Invalid email or verification code" + }, "keyConnectorDomain": { "message": "Dominio Key Connector" }, @@ -12857,6 +12877,21 @@ "storageFullDescription": { "message": "Hai usato tutti i $GB$ GB del tuo spazio di archiviazione crittografato. Per archiviare altri file, aggiungi altro spazio." }, + "extensionPromptHeading": { + "message": "Get the extension for easy vault access" + }, + "extensionPromptBody": { + "message": "With the browser extension installed, you'll take Bitwarden everywhere online. It'll fill in passwords, so you can log into your accounts with a single click." + }, + "extensionPromptImageAlt": { + "message": "A web browser showing the Bitwarden extension with autofill items for the current webpage." + }, + "skip": { + "message": "Skip" + }, + "downloadExtension": { + "message": "Download extension" + }, "whoCanView": { "message": "Chi può visualizzare" }, @@ -12869,6 +12904,9 @@ "enterMultipleEmailsSeparatedByComma": { "message": "Inserisci più indirizzi email separandoli con virgole." }, + "emailsRequiredChangeAccessType": { + "message": "Email verification requires at least one email address. To remove all emails, change the access type above." + }, "emailPlaceholder": { "message": "user@bitwarden.com , user@acme.com" }, @@ -12893,6 +12931,18 @@ "invalidSendPassword": { "message": "Password del Send non valida" }, + "vaultWelcomeDialogTitle": { + "message": "You're in! Welcome to Bitwarden" + }, + "vaultWelcomeDialogDescription": { + "message": "Store all your passwords and personal info in your Bitwarden vault. We'll show you around." + }, + "vaultWelcomeDialogPrimaryCta": { + "message": "Start tour" + }, + "vaultWelcomeDialogDismissCta": { + "message": "Skip" + }, "sendPasswordHelperText": { "message": "Individuals will need to enter the password to view this Send", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." @@ -12948,5 +12998,23 @@ }, "paymentMethodUpdateError": { "message": "There was an error updating your payment method." + }, + "sendPasswordInvalidAskOwner": { + "message": "Invalid password. Ask the sender for the password needed to access this Send.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "sendExpiresOn": { + "message": "This Send expires at $TIME$ on $DATE$", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated.", + "placeholders": { + "time": { + "content": "$1", + "example": "10:00 AM" + }, + "date": { + "content": "$2", + "example": "Jan 1, 1970" + } + } } } diff --git a/apps/web/src/locales/ja/messages.json b/apps/web/src/locales/ja/messages.json index d86a7dce648..b3b5a975ec0 100644 --- a/apps/web/src/locales/ja/messages.json +++ b/apps/web/src/locales/ja/messages.json @@ -4337,6 +4337,15 @@ } } }, + "automaticallyConfirmedUserId": { + "message": "Automatically confirmed user $ID$.", + "placeholders": { + "id": { + "content": "$1", + "example": "John Smith" + } + } + }, "editedUserId": { "message": "ユーザー「$ID$」の編集", "placeholders": { @@ -4596,29 +4605,26 @@ "generatingYourAccessIntelligence": { "message": "Generating your Access Intelligence..." }, - "fetchingMemberData": { - "message": "Fetching member data..." - }, - "analyzingPasswordHealth": { - "message": "Analyzing password health..." - }, - "calculatingRiskScores": { - "message": "Calculating risk scores..." - }, - "generatingReportData": { - "message": "Generating report data..." - }, - "savingReport": { - "message": "Saving report..." - }, - "compilingInsights": { - "message": "Compiling insights..." - }, "loadingProgress": { "message": "Loading progress" }, - "thisMightTakeFewMinutes": { - "message": "This might take a few minutes." + "reviewingMemberData": { + "message": "Reviewing member data..." + }, + "analyzingPasswords": { + "message": "Analyzing passwords..." + }, + "calculatingRisks": { + "message": "Calculating risks..." + }, + "generatingReports": { + "message": "Generating reports..." + }, + "compilingInsightsProgress": { + "message": "Compiling insights..." + }, + "reportGenerationDone": { + "message": "Done!" }, "riskInsightsRunReport": { "message": "Run report" @@ -5849,10 +5855,6 @@ "message": "パスワードがわかりませんか?このSendにアクセスするには送信者にパスワードをご確認ください。", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, - "sendHiddenByDefault": { - "message": "このSendはデフォルトでは非表示になっています。下のボタンで表示・非表示が切り替え可能です。", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, "downloadAttachments": { "message": "添付ファイルをダウンロード" }, @@ -6145,6 +6147,21 @@ "autoConfirmCheckBoxLabel": { "message": "I accept these risks and policy updates" }, + "autoConfirmEnabledByAdmin": { + "message": "Turned on Automatic user confirmation setting" + }, + "autoConfirmDisabledByAdmin": { + "message": "Turned off Automatic user confirmation setting" + }, + "autoConfirmEnabledByPortal": { + "message": "Added Automatic user confirmation policy" + }, + "autoConfirmDisabledByPortal": { + "message": "Removed Automatic user confirmation policy" + }, + "system": { + "message": "System" + }, "personalOwnership": { "message": "個別の保管庫を削除" }, @@ -7400,6 +7417,9 @@ "invalidVerificationCode": { "message": "認証コードが間違っています" }, + "invalidEmailOrVerificationCode": { + "message": "Invalid email or verification code" + }, "keyConnectorDomain": { "message": "Key Connector domain" }, @@ -12857,6 +12877,21 @@ "storageFullDescription": { "message": "You have used all $GB$ GB of your encrypted storage. To continue storing files, add more storage." }, + "extensionPromptHeading": { + "message": "Get the extension for easy vault access" + }, + "extensionPromptBody": { + "message": "With the browser extension installed, you'll take Bitwarden everywhere online. It'll fill in passwords, so you can log into your accounts with a single click." + }, + "extensionPromptImageAlt": { + "message": "A web browser showing the Bitwarden extension with autofill items for the current webpage." + }, + "skip": { + "message": "Skip" + }, + "downloadExtension": { + "message": "Download extension" + }, "whoCanView": { "message": "Who can view" }, @@ -12869,6 +12904,9 @@ "enterMultipleEmailsSeparatedByComma": { "message": "Enter multiple emails by separating with a comma." }, + "emailsRequiredChangeAccessType": { + "message": "Email verification requires at least one email address. To remove all emails, change the access type above." + }, "emailPlaceholder": { "message": "user@bitwarden.com , user@acme.com" }, @@ -12893,6 +12931,18 @@ "invalidSendPassword": { "message": "Invalid Send password" }, + "vaultWelcomeDialogTitle": { + "message": "You're in! Welcome to Bitwarden" + }, + "vaultWelcomeDialogDescription": { + "message": "Store all your passwords and personal info in your Bitwarden vault. We'll show you around." + }, + "vaultWelcomeDialogPrimaryCta": { + "message": "Start tour" + }, + "vaultWelcomeDialogDismissCta": { + "message": "Skip" + }, "sendPasswordHelperText": { "message": "Individuals will need to enter the password to view this Send", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." @@ -12948,5 +12998,23 @@ }, "paymentMethodUpdateError": { "message": "There was an error updating your payment method." + }, + "sendPasswordInvalidAskOwner": { + "message": "Invalid password. Ask the sender for the password needed to access this Send.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "sendExpiresOn": { + "message": "This Send expires at $TIME$ on $DATE$", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated.", + "placeholders": { + "time": { + "content": "$1", + "example": "10:00 AM" + }, + "date": { + "content": "$2", + "example": "Jan 1, 1970" + } + } } } diff --git a/apps/web/src/locales/ka/messages.json b/apps/web/src/locales/ka/messages.json index 915ca5d6cba..39a28d2a0c7 100644 --- a/apps/web/src/locales/ka/messages.json +++ b/apps/web/src/locales/ka/messages.json @@ -4337,6 +4337,15 @@ } } }, + "automaticallyConfirmedUserId": { + "message": "Automatically confirmed user $ID$.", + "placeholders": { + "id": { + "content": "$1", + "example": "John Smith" + } + } + }, "editedUserId": { "message": "Edited user $ID$.", "placeholders": { @@ -4596,29 +4605,26 @@ "generatingYourAccessIntelligence": { "message": "Generating your Access Intelligence..." }, - "fetchingMemberData": { - "message": "Fetching member data..." - }, - "analyzingPasswordHealth": { - "message": "Analyzing password health..." - }, - "calculatingRiskScores": { - "message": "Calculating risk scores..." - }, - "generatingReportData": { - "message": "Generating report data..." - }, - "savingReport": { - "message": "Saving report..." - }, - "compilingInsights": { - "message": "Compiling insights..." - }, "loadingProgress": { "message": "Loading progress" }, - "thisMightTakeFewMinutes": { - "message": "This might take a few minutes." + "reviewingMemberData": { + "message": "Reviewing member data..." + }, + "analyzingPasswords": { + "message": "Analyzing passwords..." + }, + "calculatingRisks": { + "message": "Calculating risks..." + }, + "generatingReports": { + "message": "Generating reports..." + }, + "compilingInsightsProgress": { + "message": "Compiling insights..." + }, + "reportGenerationDone": { + "message": "Done!" }, "riskInsightsRunReport": { "message": "Run report" @@ -5849,10 +5855,6 @@ "message": "Don't know the password? Ask the sender for the password needed to access this Send.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, - "sendHiddenByDefault": { - "message": "This Send is hidden by default. You can toggle its visibility using the button below.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, "downloadAttachments": { "message": "Download attachments" }, @@ -6145,6 +6147,21 @@ "autoConfirmCheckBoxLabel": { "message": "I accept these risks and policy updates" }, + "autoConfirmEnabledByAdmin": { + "message": "Turned on Automatic user confirmation setting" + }, + "autoConfirmDisabledByAdmin": { + "message": "Turned off Automatic user confirmation setting" + }, + "autoConfirmEnabledByPortal": { + "message": "Added Automatic user confirmation policy" + }, + "autoConfirmDisabledByPortal": { + "message": "Removed Automatic user confirmation policy" + }, + "system": { + "message": "System" + }, "personalOwnership": { "message": "Remove individual vault" }, @@ -7400,6 +7417,9 @@ "invalidVerificationCode": { "message": "Invalid verification code" }, + "invalidEmailOrVerificationCode": { + "message": "Invalid email or verification code" + }, "keyConnectorDomain": { "message": "Key Connector domain" }, @@ -12857,6 +12877,21 @@ "storageFullDescription": { "message": "You have used all $GB$ GB of your encrypted storage. To continue storing files, add more storage." }, + "extensionPromptHeading": { + "message": "Get the extension for easy vault access" + }, + "extensionPromptBody": { + "message": "With the browser extension installed, you'll take Bitwarden everywhere online. It'll fill in passwords, so you can log into your accounts with a single click." + }, + "extensionPromptImageAlt": { + "message": "A web browser showing the Bitwarden extension with autofill items for the current webpage." + }, + "skip": { + "message": "Skip" + }, + "downloadExtension": { + "message": "Download extension" + }, "whoCanView": { "message": "Who can view" }, @@ -12869,6 +12904,9 @@ "enterMultipleEmailsSeparatedByComma": { "message": "Enter multiple emails by separating with a comma." }, + "emailsRequiredChangeAccessType": { + "message": "Email verification requires at least one email address. To remove all emails, change the access type above." + }, "emailPlaceholder": { "message": "user@bitwarden.com , user@acme.com" }, @@ -12893,6 +12931,18 @@ "invalidSendPassword": { "message": "Invalid Send password" }, + "vaultWelcomeDialogTitle": { + "message": "You're in! Welcome to Bitwarden" + }, + "vaultWelcomeDialogDescription": { + "message": "Store all your passwords and personal info in your Bitwarden vault. We'll show you around." + }, + "vaultWelcomeDialogPrimaryCta": { + "message": "Start tour" + }, + "vaultWelcomeDialogDismissCta": { + "message": "Skip" + }, "sendPasswordHelperText": { "message": "Individuals will need to enter the password to view this Send", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." @@ -12948,5 +12998,23 @@ }, "paymentMethodUpdateError": { "message": "There was an error updating your payment method." + }, + "sendPasswordInvalidAskOwner": { + "message": "Invalid password. Ask the sender for the password needed to access this Send.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "sendExpiresOn": { + "message": "This Send expires at $TIME$ on $DATE$", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated.", + "placeholders": { + "time": { + "content": "$1", + "example": "10:00 AM" + }, + "date": { + "content": "$2", + "example": "Jan 1, 1970" + } + } } } diff --git a/apps/web/src/locales/km/messages.json b/apps/web/src/locales/km/messages.json index bc0e3b74cb6..4e6a8265ad6 100644 --- a/apps/web/src/locales/km/messages.json +++ b/apps/web/src/locales/km/messages.json @@ -4337,6 +4337,15 @@ } } }, + "automaticallyConfirmedUserId": { + "message": "Automatically confirmed user $ID$.", + "placeholders": { + "id": { + "content": "$1", + "example": "John Smith" + } + } + }, "editedUserId": { "message": "Edited user $ID$.", "placeholders": { @@ -4596,29 +4605,26 @@ "generatingYourAccessIntelligence": { "message": "Generating your Access Intelligence..." }, - "fetchingMemberData": { - "message": "Fetching member data..." - }, - "analyzingPasswordHealth": { - "message": "Analyzing password health..." - }, - "calculatingRiskScores": { - "message": "Calculating risk scores..." - }, - "generatingReportData": { - "message": "Generating report data..." - }, - "savingReport": { - "message": "Saving report..." - }, - "compilingInsights": { - "message": "Compiling insights..." - }, "loadingProgress": { "message": "Loading progress" }, - "thisMightTakeFewMinutes": { - "message": "This might take a few minutes." + "reviewingMemberData": { + "message": "Reviewing member data..." + }, + "analyzingPasswords": { + "message": "Analyzing passwords..." + }, + "calculatingRisks": { + "message": "Calculating risks..." + }, + "generatingReports": { + "message": "Generating reports..." + }, + "compilingInsightsProgress": { + "message": "Compiling insights..." + }, + "reportGenerationDone": { + "message": "Done!" }, "riskInsightsRunReport": { "message": "Run report" @@ -5849,10 +5855,6 @@ "message": "Don't know the password? Ask the sender for the password needed to access this Send.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, - "sendHiddenByDefault": { - "message": "This Send is hidden by default. You can toggle its visibility using the button below.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, "downloadAttachments": { "message": "Download attachments" }, @@ -6145,6 +6147,21 @@ "autoConfirmCheckBoxLabel": { "message": "I accept these risks and policy updates" }, + "autoConfirmEnabledByAdmin": { + "message": "Turned on Automatic user confirmation setting" + }, + "autoConfirmDisabledByAdmin": { + "message": "Turned off Automatic user confirmation setting" + }, + "autoConfirmEnabledByPortal": { + "message": "Added Automatic user confirmation policy" + }, + "autoConfirmDisabledByPortal": { + "message": "Removed Automatic user confirmation policy" + }, + "system": { + "message": "System" + }, "personalOwnership": { "message": "Remove individual vault" }, @@ -7400,6 +7417,9 @@ "invalidVerificationCode": { "message": "Invalid verification code" }, + "invalidEmailOrVerificationCode": { + "message": "Invalid email or verification code" + }, "keyConnectorDomain": { "message": "Key Connector domain" }, @@ -12857,6 +12877,21 @@ "storageFullDescription": { "message": "You have used all $GB$ GB of your encrypted storage. To continue storing files, add more storage." }, + "extensionPromptHeading": { + "message": "Get the extension for easy vault access" + }, + "extensionPromptBody": { + "message": "With the browser extension installed, you'll take Bitwarden everywhere online. It'll fill in passwords, so you can log into your accounts with a single click." + }, + "extensionPromptImageAlt": { + "message": "A web browser showing the Bitwarden extension with autofill items for the current webpage." + }, + "skip": { + "message": "Skip" + }, + "downloadExtension": { + "message": "Download extension" + }, "whoCanView": { "message": "Who can view" }, @@ -12869,6 +12904,9 @@ "enterMultipleEmailsSeparatedByComma": { "message": "Enter multiple emails by separating with a comma." }, + "emailsRequiredChangeAccessType": { + "message": "Email verification requires at least one email address. To remove all emails, change the access type above." + }, "emailPlaceholder": { "message": "user@bitwarden.com , user@acme.com" }, @@ -12893,6 +12931,18 @@ "invalidSendPassword": { "message": "Invalid Send password" }, + "vaultWelcomeDialogTitle": { + "message": "You're in! Welcome to Bitwarden" + }, + "vaultWelcomeDialogDescription": { + "message": "Store all your passwords and personal info in your Bitwarden vault. We'll show you around." + }, + "vaultWelcomeDialogPrimaryCta": { + "message": "Start tour" + }, + "vaultWelcomeDialogDismissCta": { + "message": "Skip" + }, "sendPasswordHelperText": { "message": "Individuals will need to enter the password to view this Send", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." @@ -12948,5 +12998,23 @@ }, "paymentMethodUpdateError": { "message": "There was an error updating your payment method." + }, + "sendPasswordInvalidAskOwner": { + "message": "Invalid password. Ask the sender for the password needed to access this Send.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "sendExpiresOn": { + "message": "This Send expires at $TIME$ on $DATE$", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated.", + "placeholders": { + "time": { + "content": "$1", + "example": "10:00 AM" + }, + "date": { + "content": "$2", + "example": "Jan 1, 1970" + } + } } } diff --git a/apps/web/src/locales/kn/messages.json b/apps/web/src/locales/kn/messages.json index bd77a975193..85e79eba91d 100644 --- a/apps/web/src/locales/kn/messages.json +++ b/apps/web/src/locales/kn/messages.json @@ -4337,6 +4337,15 @@ } } }, + "automaticallyConfirmedUserId": { + "message": "Automatically confirmed user $ID$.", + "placeholders": { + "id": { + "content": "$1", + "example": "John Smith" + } + } + }, "editedUserId": { "message": "ತಿದ್ಸಿದ ಬಳಕೆದಾರ $ID$.", "placeholders": { @@ -4596,29 +4605,26 @@ "generatingYourAccessIntelligence": { "message": "Generating your Access Intelligence..." }, - "fetchingMemberData": { - "message": "Fetching member data..." - }, - "analyzingPasswordHealth": { - "message": "Analyzing password health..." - }, - "calculatingRiskScores": { - "message": "Calculating risk scores..." - }, - "generatingReportData": { - "message": "Generating report data..." - }, - "savingReport": { - "message": "Saving report..." - }, - "compilingInsights": { - "message": "Compiling insights..." - }, "loadingProgress": { "message": "Loading progress" }, - "thisMightTakeFewMinutes": { - "message": "This might take a few minutes." + "reviewingMemberData": { + "message": "Reviewing member data..." + }, + "analyzingPasswords": { + "message": "Analyzing passwords..." + }, + "calculatingRisks": { + "message": "Calculating risks..." + }, + "generatingReports": { + "message": "Generating reports..." + }, + "compilingInsightsProgress": { + "message": "Compiling insights..." + }, + "reportGenerationDone": { + "message": "Done!" }, "riskInsightsRunReport": { "message": "Run report" @@ -5849,10 +5855,6 @@ "message": "ಪಾಸ್ವರ್ಡ್ ತಿಳಿದಿಲ್ಲವೇ? ಈ ಕಳುಹಿಸುವಿಕೆಯನ್ನು ಪ್ರವೇಶಿಸಲು ಅಗತ್ಯವಿರುವ ಪಾಸ್‌ವರ್ಡ್‌ಗಾಗಿ ಕಳುಹಿಸುವವರನ್ನು ಕೇಳಿ.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, - "sendHiddenByDefault": { - "message": "ಈ ಕಳುಹಿಸುವಿಕೆಯನ್ನು ಪೂರ್ವನಿಯೋಜಿತವಾಗಿ ಮರೆಮಾಡಲಾಗಿದೆ. ಕೆಳಗಿನ ಬಟನ್ ಬಳಸಿ ನೀವು ಅದರ ಗೋಚರತೆಯನ್ನು ಟಾಗಲ್ ಮಾಡಬಹುದು.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, "downloadAttachments": { "message": "Download attachments" }, @@ -6145,6 +6147,21 @@ "autoConfirmCheckBoxLabel": { "message": "I accept these risks and policy updates" }, + "autoConfirmEnabledByAdmin": { + "message": "Turned on Automatic user confirmation setting" + }, + "autoConfirmDisabledByAdmin": { + "message": "Turned off Automatic user confirmation setting" + }, + "autoConfirmEnabledByPortal": { + "message": "Added Automatic user confirmation policy" + }, + "autoConfirmDisabledByPortal": { + "message": "Removed Automatic user confirmation policy" + }, + "system": { + "message": "System" + }, "personalOwnership": { "message": "ವೈಯಕ್ತಿಕ ಮಾಲೀಕತ್ವ" }, @@ -7400,6 +7417,9 @@ "invalidVerificationCode": { "message": "Invalid verification code" }, + "invalidEmailOrVerificationCode": { + "message": "Invalid email or verification code" + }, "keyConnectorDomain": { "message": "Key Connector domain" }, @@ -12857,6 +12877,21 @@ "storageFullDescription": { "message": "You have used all $GB$ GB of your encrypted storage. To continue storing files, add more storage." }, + "extensionPromptHeading": { + "message": "Get the extension for easy vault access" + }, + "extensionPromptBody": { + "message": "With the browser extension installed, you'll take Bitwarden everywhere online. It'll fill in passwords, so you can log into your accounts with a single click." + }, + "extensionPromptImageAlt": { + "message": "A web browser showing the Bitwarden extension with autofill items for the current webpage." + }, + "skip": { + "message": "Skip" + }, + "downloadExtension": { + "message": "Download extension" + }, "whoCanView": { "message": "Who can view" }, @@ -12869,6 +12904,9 @@ "enterMultipleEmailsSeparatedByComma": { "message": "Enter multiple emails by separating with a comma." }, + "emailsRequiredChangeAccessType": { + "message": "Email verification requires at least one email address. To remove all emails, change the access type above." + }, "emailPlaceholder": { "message": "user@bitwarden.com , user@acme.com" }, @@ -12893,6 +12931,18 @@ "invalidSendPassword": { "message": "Invalid Send password" }, + "vaultWelcomeDialogTitle": { + "message": "You're in! Welcome to Bitwarden" + }, + "vaultWelcomeDialogDescription": { + "message": "Store all your passwords and personal info in your Bitwarden vault. We'll show you around." + }, + "vaultWelcomeDialogPrimaryCta": { + "message": "Start tour" + }, + "vaultWelcomeDialogDismissCta": { + "message": "Skip" + }, "sendPasswordHelperText": { "message": "Individuals will need to enter the password to view this Send", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." @@ -12948,5 +12998,23 @@ }, "paymentMethodUpdateError": { "message": "There was an error updating your payment method." + }, + "sendPasswordInvalidAskOwner": { + "message": "Invalid password. Ask the sender for the password needed to access this Send.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "sendExpiresOn": { + "message": "This Send expires at $TIME$ on $DATE$", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated.", + "placeholders": { + "time": { + "content": "$1", + "example": "10:00 AM" + }, + "date": { + "content": "$2", + "example": "Jan 1, 1970" + } + } } } diff --git a/apps/web/src/locales/ko/messages.json b/apps/web/src/locales/ko/messages.json index 0af228dd821..273bdddadab 100644 --- a/apps/web/src/locales/ko/messages.json +++ b/apps/web/src/locales/ko/messages.json @@ -4337,6 +4337,15 @@ } } }, + "automaticallyConfirmedUserId": { + "message": "Automatically confirmed user $ID$.", + "placeholders": { + "id": { + "content": "$1", + "example": "John Smith" + } + } + }, "editedUserId": { "message": "$ID$ 사용자를 편집했습니다.", "placeholders": { @@ -4596,29 +4605,26 @@ "generatingYourAccessIntelligence": { "message": "Generating your Access Intelligence..." }, - "fetchingMemberData": { - "message": "Fetching member data..." - }, - "analyzingPasswordHealth": { - "message": "Analyzing password health..." - }, - "calculatingRiskScores": { - "message": "Calculating risk scores..." - }, - "generatingReportData": { - "message": "Generating report data..." - }, - "savingReport": { - "message": "Saving report..." - }, - "compilingInsights": { - "message": "Compiling insights..." - }, "loadingProgress": { "message": "Loading progress" }, - "thisMightTakeFewMinutes": { - "message": "This might take a few minutes." + "reviewingMemberData": { + "message": "Reviewing member data..." + }, + "analyzingPasswords": { + "message": "Analyzing passwords..." + }, + "calculatingRisks": { + "message": "Calculating risks..." + }, + "generatingReports": { + "message": "Generating reports..." + }, + "compilingInsightsProgress": { + "message": "Compiling insights..." + }, + "reportGenerationDone": { + "message": "Done!" }, "riskInsightsRunReport": { "message": "Run report" @@ -5849,10 +5855,6 @@ "message": "비밀번호를 모르시나요? 보낸 사람에게 Send에 접근할 수 있는 비밀번호를 요청하세요.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, - "sendHiddenByDefault": { - "message": "이 Send는 기본적으로 숨겨져 있습니다. 아래의 버튼을 눌러 공개 여부를 전환할 수 있습니다.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, "downloadAttachments": { "message": "첨부 파일 다운로드" }, @@ -6145,6 +6147,21 @@ "autoConfirmCheckBoxLabel": { "message": "I accept these risks and policy updates" }, + "autoConfirmEnabledByAdmin": { + "message": "Turned on Automatic user confirmation setting" + }, + "autoConfirmDisabledByAdmin": { + "message": "Turned off Automatic user confirmation setting" + }, + "autoConfirmEnabledByPortal": { + "message": "Added Automatic user confirmation policy" + }, + "autoConfirmDisabledByPortal": { + "message": "Removed Automatic user confirmation policy" + }, + "system": { + "message": "System" + }, "personalOwnership": { "message": "개인 소유권" }, @@ -7400,6 +7417,9 @@ "invalidVerificationCode": { "message": "유효하지 않은 확인 코드" }, + "invalidEmailOrVerificationCode": { + "message": "Invalid email or verification code" + }, "keyConnectorDomain": { "message": "Key Connector domain" }, @@ -12857,6 +12877,21 @@ "storageFullDescription": { "message": "You have used all $GB$ GB of your encrypted storage. To continue storing files, add more storage." }, + "extensionPromptHeading": { + "message": "Get the extension for easy vault access" + }, + "extensionPromptBody": { + "message": "With the browser extension installed, you'll take Bitwarden everywhere online. It'll fill in passwords, so you can log into your accounts with a single click." + }, + "extensionPromptImageAlt": { + "message": "A web browser showing the Bitwarden extension with autofill items for the current webpage." + }, + "skip": { + "message": "Skip" + }, + "downloadExtension": { + "message": "Download extension" + }, "whoCanView": { "message": "Who can view" }, @@ -12869,6 +12904,9 @@ "enterMultipleEmailsSeparatedByComma": { "message": "Enter multiple emails by separating with a comma." }, + "emailsRequiredChangeAccessType": { + "message": "Email verification requires at least one email address. To remove all emails, change the access type above." + }, "emailPlaceholder": { "message": "user@bitwarden.com , user@acme.com" }, @@ -12893,6 +12931,18 @@ "invalidSendPassword": { "message": "Invalid Send password" }, + "vaultWelcomeDialogTitle": { + "message": "You're in! Welcome to Bitwarden" + }, + "vaultWelcomeDialogDescription": { + "message": "Store all your passwords and personal info in your Bitwarden vault. We'll show you around." + }, + "vaultWelcomeDialogPrimaryCta": { + "message": "Start tour" + }, + "vaultWelcomeDialogDismissCta": { + "message": "Skip" + }, "sendPasswordHelperText": { "message": "Individuals will need to enter the password to view this Send", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." @@ -12948,5 +12998,23 @@ }, "paymentMethodUpdateError": { "message": "There was an error updating your payment method." + }, + "sendPasswordInvalidAskOwner": { + "message": "Invalid password. Ask the sender for the password needed to access this Send.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "sendExpiresOn": { + "message": "This Send expires at $TIME$ on $DATE$", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated.", + "placeholders": { + "time": { + "content": "$1", + "example": "10:00 AM" + }, + "date": { + "content": "$2", + "example": "Jan 1, 1970" + } + } } } diff --git a/apps/web/src/locales/lv/messages.json b/apps/web/src/locales/lv/messages.json index f12dda40ea3..26f960a29d7 100644 --- a/apps/web/src/locales/lv/messages.json +++ b/apps/web/src/locales/lv/messages.json @@ -3339,13 +3339,13 @@ "message": "Abonements tika atjaunots." }, "resubscribe": { - "message": "Resubscribe" + "message": "Atsākt abonēšanu" }, "yourSubscriptionIsExpired": { - "message": "Your subscription is expired" + "message": "Abonements ir beidzies" }, "yourSubscriptionIsCanceled": { - "message": "Your subscription is canceled" + "message": "Abonements ir atcelts" }, "cancelConfirmation": { "message": "Vai tiešām atcelt? Tiks zaudēta piekļuve visām abonementa iespējām pēc pašreizējā norēķinu laika posma beigām." @@ -4337,6 +4337,15 @@ } } }, + "automaticallyConfirmedUserId": { + "message": "Automatically confirmed user $ID$.", + "placeholders": { + "id": { + "content": "$1", + "example": "John Smith" + } + } + }, "editedUserId": { "message": "Labots lietotājs $ID$.", "placeholders": { @@ -4596,29 +4605,26 @@ "generatingYourAccessIntelligence": { "message": "Izveido informāciju par Tavu piekļuvi…" }, - "fetchingMemberData": { - "message": "Iegūst dalībnieku datus…" - }, - "analyzingPasswordHealth": { - "message": "Izvērtē paroļu veselību…" - }, - "calculatingRiskScores": { - "message": "Aprēķina risku novērtējumu…" - }, - "generatingReportData": { - "message": "Izveido atskaites datus…" - }, - "savingReport": { - "message": "Saglabā atskaiti…" - }, - "compilingInsights": { - "message": "Apkopo ieskatus…" - }, "loadingProgress": { "message": "Ielādē virzību" }, - "thisMightTakeFewMinutes": { - "message": "Tas var aizņemt dažas minūtes." + "reviewingMemberData": { + "message": "Pārskata dalībnieku datus…" + }, + "analyzingPasswords": { + "message": "Izvērtē paroles…" + }, + "calculatingRisks": { + "message": "Aprēķina riskus…" + }, + "generatingReports": { + "message": "Izveido pārskatus…" + }, + "compilingInsightsProgress": { + "message": "Apkopo ieskatus…" + }, + "reportGenerationDone": { + "message": "Gatavs." }, "riskInsightsRunReport": { "message": "Izveidot atskaiti" @@ -5849,10 +5855,6 @@ "message": "Nezini paroli? Vaicā sūtītājam paroli, kas ir nepieciešama, lai piekļūtu šim Send!", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, - "sendHiddenByDefault": { - "message": "Šis Send ir paslēpts pēc noklusējuma. Tā redzamību var pārslēgt ar zemāk esošo pogu.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, "downloadAttachments": { "message": "Lejupielādēt pielikumus" }, @@ -6145,6 +6147,21 @@ "autoConfirmCheckBoxLabel": { "message": "Es pieņemu šos riskus un pamatnostādnes atjauninājumus" }, + "autoConfirmEnabledByAdmin": { + "message": "Turned on Automatic user confirmation setting" + }, + "autoConfirmDisabledByAdmin": { + "message": "Turned off Automatic user confirmation setting" + }, + "autoConfirmEnabledByPortal": { + "message": "Added Automatic user confirmation policy" + }, + "autoConfirmDisabledByPortal": { + "message": "Removed Automatic user confirmation policy" + }, + "system": { + "message": "System" + }, "personalOwnership": { "message": "Personīgās īpašumtiesības" }, @@ -6439,7 +6456,7 @@ "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "verifyYourEmailToViewThisSend": { - "message": "Verify your email to view this Send", + "message": "Jāapliecina sava e-pasta adrese, lai apskatītu šo Send", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "viewSendHiddenEmailWarning": { @@ -7400,6 +7417,9 @@ "invalidVerificationCode": { "message": "Nederīgs apliecinājuma kods" }, + "invalidEmailOrVerificationCode": { + "message": "Nederīga e-pasta adrese vai apliecinājuma kods" + }, "keyConnectorDomain": { "message": "Key Connector domēns" }, @@ -11910,10 +11930,10 @@ "message": "Šeit parādīsies arhivētie vienumi, un tie netiks iekļauti vispārējās meklēšanas iznākumos un automātiskās aizpildes ieteikumos." }, "itemArchiveToast": { - "message": "Item archived" + "message": "Vienums ievietots arhīvā" }, "itemUnarchivedToast": { - "message": "Item unarchived" + "message": "Vienums izņemts no arhīva" }, "bulkArchiveItems": { "message": "Vienumi tika arhivēti" @@ -12857,6 +12877,21 @@ "storageFullDescription": { "message": "You have used all $GB$ GB of your encrypted storage. To continue storing files, add more storage." }, + "extensionPromptHeading": { + "message": "Get the extension for easy vault access" + }, + "extensionPromptBody": { + "message": "With the browser extension installed, you'll take Bitwarden everywhere online. It'll fill in passwords, so you can log into your accounts with a single click." + }, + "extensionPromptImageAlt": { + "message": "A web browser showing the Bitwarden extension with autofill items for the current webpage." + }, + "skip": { + "message": "Skip" + }, + "downloadExtension": { + "message": "Download extension" + }, "whoCanView": { "message": "Who can view" }, @@ -12869,6 +12904,9 @@ "enterMultipleEmailsSeparatedByComma": { "message": "Enter multiple emails by separating with a comma." }, + "emailsRequiredChangeAccessType": { + "message": "E-pasta apliecināšanai ir nepieciešama vismaz viena e-pasta adrese. Lai noņemtu visas e-pasta adreses, augstāk jānomaina piekļūšanas veids." + }, "emailPlaceholder": { "message": "user@bitwarden.com , user@acme.com" }, @@ -12893,6 +12931,18 @@ "invalidSendPassword": { "message": "Invalid Send password" }, + "vaultWelcomeDialogTitle": { + "message": "You're in! Welcome to Bitwarden" + }, + "vaultWelcomeDialogDescription": { + "message": "Store all your passwords and personal info in your Bitwarden vault. We'll show you around." + }, + "vaultWelcomeDialogPrimaryCta": { + "message": "Start tour" + }, + "vaultWelcomeDialogDismissCta": { + "message": "Skip" + }, "sendPasswordHelperText": { "message": "Cilvēkiem būs jāievada parole, lai apskatītu šo Send", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." @@ -12948,5 +12998,23 @@ }, "paymentMethodUpdateError": { "message": "There was an error updating your payment method." + }, + "sendPasswordInvalidAskOwner": { + "message": "Nederīga parole. Jāvaicā nepieciešamā parole nosūtītājam, lai piekļūtu šim Send.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "sendExpiresOn": { + "message": "Šī Send derīgums beigsies $DATE$ plkst. $TIME$", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated.", + "placeholders": { + "time": { + "content": "$1", + "example": "10:00 AM" + }, + "date": { + "content": "$2", + "example": "Jan 1, 1970" + } + } } } diff --git a/apps/web/src/locales/ml/messages.json b/apps/web/src/locales/ml/messages.json index e68e7d25b85..c700f329c65 100644 --- a/apps/web/src/locales/ml/messages.json +++ b/apps/web/src/locales/ml/messages.json @@ -4337,6 +4337,15 @@ } } }, + "automaticallyConfirmedUserId": { + "message": "Automatically confirmed user $ID$.", + "placeholders": { + "id": { + "content": "$1", + "example": "John Smith" + } + } + }, "editedUserId": { "message": "Edited user $ID$.", "placeholders": { @@ -4596,29 +4605,26 @@ "generatingYourAccessIntelligence": { "message": "Generating your Access Intelligence..." }, - "fetchingMemberData": { - "message": "Fetching member data..." - }, - "analyzingPasswordHealth": { - "message": "Analyzing password health..." - }, - "calculatingRiskScores": { - "message": "Calculating risk scores..." - }, - "generatingReportData": { - "message": "Generating report data..." - }, - "savingReport": { - "message": "Saving report..." - }, - "compilingInsights": { - "message": "Compiling insights..." - }, "loadingProgress": { "message": "Loading progress" }, - "thisMightTakeFewMinutes": { - "message": "This might take a few minutes." + "reviewingMemberData": { + "message": "Reviewing member data..." + }, + "analyzingPasswords": { + "message": "Analyzing passwords..." + }, + "calculatingRisks": { + "message": "Calculating risks..." + }, + "generatingReports": { + "message": "Generating reports..." + }, + "compilingInsightsProgress": { + "message": "Compiling insights..." + }, + "reportGenerationDone": { + "message": "Done!" }, "riskInsightsRunReport": { "message": "Run report" @@ -5849,10 +5855,6 @@ "message": "പാസ്‌വേഡ് അറിയില്ലേ? ഈ അയയ്‌ക്കൽ ആക്‌സസ് ചെയ്യുന്നതിന് ആവശ്യമായ പാസ്‌വേഡിനായി അയച്ചയാളോട് ചോദിക്കുക.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, - "sendHiddenByDefault": { - "message": "ഈ Send സ്ഥിരസ്ഥിതിയായി മറച്ചിരിക്കുന്നു. ചുവടെയുള്ള ബട്ടൺ ഉപയോഗിച്ചാൽ നിങ്ങൾക്ക് അതിന്റെ ദൃശ്യപരത ടോഗിൾ ചെയ്യാൻ കഴിയും.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, "downloadAttachments": { "message": "Download attachments" }, @@ -6145,6 +6147,21 @@ "autoConfirmCheckBoxLabel": { "message": "I accept these risks and policy updates" }, + "autoConfirmEnabledByAdmin": { + "message": "Turned on Automatic user confirmation setting" + }, + "autoConfirmDisabledByAdmin": { + "message": "Turned off Automatic user confirmation setting" + }, + "autoConfirmEnabledByPortal": { + "message": "Added Automatic user confirmation policy" + }, + "autoConfirmDisabledByPortal": { + "message": "Removed Automatic user confirmation policy" + }, + "system": { + "message": "System" + }, "personalOwnership": { "message": "വ്യക്തിഗത ഉടമസ്ഥാവകാശം" }, @@ -7400,6 +7417,9 @@ "invalidVerificationCode": { "message": "Invalid verification code" }, + "invalidEmailOrVerificationCode": { + "message": "Invalid email or verification code" + }, "keyConnectorDomain": { "message": "Key Connector domain" }, @@ -12857,6 +12877,21 @@ "storageFullDescription": { "message": "You have used all $GB$ GB of your encrypted storage. To continue storing files, add more storage." }, + "extensionPromptHeading": { + "message": "Get the extension for easy vault access" + }, + "extensionPromptBody": { + "message": "With the browser extension installed, you'll take Bitwarden everywhere online. It'll fill in passwords, so you can log into your accounts with a single click." + }, + "extensionPromptImageAlt": { + "message": "A web browser showing the Bitwarden extension with autofill items for the current webpage." + }, + "skip": { + "message": "Skip" + }, + "downloadExtension": { + "message": "Download extension" + }, "whoCanView": { "message": "Who can view" }, @@ -12869,6 +12904,9 @@ "enterMultipleEmailsSeparatedByComma": { "message": "Enter multiple emails by separating with a comma." }, + "emailsRequiredChangeAccessType": { + "message": "Email verification requires at least one email address. To remove all emails, change the access type above." + }, "emailPlaceholder": { "message": "user@bitwarden.com , user@acme.com" }, @@ -12893,6 +12931,18 @@ "invalidSendPassword": { "message": "Invalid Send password" }, + "vaultWelcomeDialogTitle": { + "message": "You're in! Welcome to Bitwarden" + }, + "vaultWelcomeDialogDescription": { + "message": "Store all your passwords and personal info in your Bitwarden vault. We'll show you around." + }, + "vaultWelcomeDialogPrimaryCta": { + "message": "Start tour" + }, + "vaultWelcomeDialogDismissCta": { + "message": "Skip" + }, "sendPasswordHelperText": { "message": "Individuals will need to enter the password to view this Send", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." @@ -12948,5 +12998,23 @@ }, "paymentMethodUpdateError": { "message": "There was an error updating your payment method." + }, + "sendPasswordInvalidAskOwner": { + "message": "Invalid password. Ask the sender for the password needed to access this Send.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "sendExpiresOn": { + "message": "This Send expires at $TIME$ on $DATE$", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated.", + "placeholders": { + "time": { + "content": "$1", + "example": "10:00 AM" + }, + "date": { + "content": "$2", + "example": "Jan 1, 1970" + } + } } } diff --git a/apps/web/src/locales/mr/messages.json b/apps/web/src/locales/mr/messages.json index 2abd88c1169..1fc1ef73f4b 100644 --- a/apps/web/src/locales/mr/messages.json +++ b/apps/web/src/locales/mr/messages.json @@ -4337,6 +4337,15 @@ } } }, + "automaticallyConfirmedUserId": { + "message": "Automatically confirmed user $ID$.", + "placeholders": { + "id": { + "content": "$1", + "example": "John Smith" + } + } + }, "editedUserId": { "message": "Edited user $ID$.", "placeholders": { @@ -4596,29 +4605,26 @@ "generatingYourAccessIntelligence": { "message": "Generating your Access Intelligence..." }, - "fetchingMemberData": { - "message": "Fetching member data..." - }, - "analyzingPasswordHealth": { - "message": "Analyzing password health..." - }, - "calculatingRiskScores": { - "message": "Calculating risk scores..." - }, - "generatingReportData": { - "message": "Generating report data..." - }, - "savingReport": { - "message": "Saving report..." - }, - "compilingInsights": { - "message": "Compiling insights..." - }, "loadingProgress": { "message": "Loading progress" }, - "thisMightTakeFewMinutes": { - "message": "This might take a few minutes." + "reviewingMemberData": { + "message": "Reviewing member data..." + }, + "analyzingPasswords": { + "message": "Analyzing passwords..." + }, + "calculatingRisks": { + "message": "Calculating risks..." + }, + "generatingReports": { + "message": "Generating reports..." + }, + "compilingInsightsProgress": { + "message": "Compiling insights..." + }, + "reportGenerationDone": { + "message": "Done!" }, "riskInsightsRunReport": { "message": "Run report" @@ -5849,10 +5855,6 @@ "message": "Don't know the password? Ask the sender for the password needed to access this Send.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, - "sendHiddenByDefault": { - "message": "This Send is hidden by default. You can toggle its visibility using the button below.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, "downloadAttachments": { "message": "Download attachments" }, @@ -6145,6 +6147,21 @@ "autoConfirmCheckBoxLabel": { "message": "I accept these risks and policy updates" }, + "autoConfirmEnabledByAdmin": { + "message": "Turned on Automatic user confirmation setting" + }, + "autoConfirmDisabledByAdmin": { + "message": "Turned off Automatic user confirmation setting" + }, + "autoConfirmEnabledByPortal": { + "message": "Added Automatic user confirmation policy" + }, + "autoConfirmDisabledByPortal": { + "message": "Removed Automatic user confirmation policy" + }, + "system": { + "message": "System" + }, "personalOwnership": { "message": "Remove individual vault" }, @@ -7400,6 +7417,9 @@ "invalidVerificationCode": { "message": "Invalid verification code" }, + "invalidEmailOrVerificationCode": { + "message": "Invalid email or verification code" + }, "keyConnectorDomain": { "message": "Key Connector domain" }, @@ -12857,6 +12877,21 @@ "storageFullDescription": { "message": "You have used all $GB$ GB of your encrypted storage. To continue storing files, add more storage." }, + "extensionPromptHeading": { + "message": "Get the extension for easy vault access" + }, + "extensionPromptBody": { + "message": "With the browser extension installed, you'll take Bitwarden everywhere online. It'll fill in passwords, so you can log into your accounts with a single click." + }, + "extensionPromptImageAlt": { + "message": "A web browser showing the Bitwarden extension with autofill items for the current webpage." + }, + "skip": { + "message": "Skip" + }, + "downloadExtension": { + "message": "Download extension" + }, "whoCanView": { "message": "Who can view" }, @@ -12869,6 +12904,9 @@ "enterMultipleEmailsSeparatedByComma": { "message": "Enter multiple emails by separating with a comma." }, + "emailsRequiredChangeAccessType": { + "message": "Email verification requires at least one email address. To remove all emails, change the access type above." + }, "emailPlaceholder": { "message": "user@bitwarden.com , user@acme.com" }, @@ -12893,6 +12931,18 @@ "invalidSendPassword": { "message": "Invalid Send password" }, + "vaultWelcomeDialogTitle": { + "message": "You're in! Welcome to Bitwarden" + }, + "vaultWelcomeDialogDescription": { + "message": "Store all your passwords and personal info in your Bitwarden vault. We'll show you around." + }, + "vaultWelcomeDialogPrimaryCta": { + "message": "Start tour" + }, + "vaultWelcomeDialogDismissCta": { + "message": "Skip" + }, "sendPasswordHelperText": { "message": "Individuals will need to enter the password to view this Send", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." @@ -12948,5 +12998,23 @@ }, "paymentMethodUpdateError": { "message": "There was an error updating your payment method." + }, + "sendPasswordInvalidAskOwner": { + "message": "Invalid password. Ask the sender for the password needed to access this Send.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "sendExpiresOn": { + "message": "This Send expires at $TIME$ on $DATE$", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated.", + "placeholders": { + "time": { + "content": "$1", + "example": "10:00 AM" + }, + "date": { + "content": "$2", + "example": "Jan 1, 1970" + } + } } } diff --git a/apps/web/src/locales/my/messages.json b/apps/web/src/locales/my/messages.json index bc0e3b74cb6..4e6a8265ad6 100644 --- a/apps/web/src/locales/my/messages.json +++ b/apps/web/src/locales/my/messages.json @@ -4337,6 +4337,15 @@ } } }, + "automaticallyConfirmedUserId": { + "message": "Automatically confirmed user $ID$.", + "placeholders": { + "id": { + "content": "$1", + "example": "John Smith" + } + } + }, "editedUserId": { "message": "Edited user $ID$.", "placeholders": { @@ -4596,29 +4605,26 @@ "generatingYourAccessIntelligence": { "message": "Generating your Access Intelligence..." }, - "fetchingMemberData": { - "message": "Fetching member data..." - }, - "analyzingPasswordHealth": { - "message": "Analyzing password health..." - }, - "calculatingRiskScores": { - "message": "Calculating risk scores..." - }, - "generatingReportData": { - "message": "Generating report data..." - }, - "savingReport": { - "message": "Saving report..." - }, - "compilingInsights": { - "message": "Compiling insights..." - }, "loadingProgress": { "message": "Loading progress" }, - "thisMightTakeFewMinutes": { - "message": "This might take a few minutes." + "reviewingMemberData": { + "message": "Reviewing member data..." + }, + "analyzingPasswords": { + "message": "Analyzing passwords..." + }, + "calculatingRisks": { + "message": "Calculating risks..." + }, + "generatingReports": { + "message": "Generating reports..." + }, + "compilingInsightsProgress": { + "message": "Compiling insights..." + }, + "reportGenerationDone": { + "message": "Done!" }, "riskInsightsRunReport": { "message": "Run report" @@ -5849,10 +5855,6 @@ "message": "Don't know the password? Ask the sender for the password needed to access this Send.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, - "sendHiddenByDefault": { - "message": "This Send is hidden by default. You can toggle its visibility using the button below.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, "downloadAttachments": { "message": "Download attachments" }, @@ -6145,6 +6147,21 @@ "autoConfirmCheckBoxLabel": { "message": "I accept these risks and policy updates" }, + "autoConfirmEnabledByAdmin": { + "message": "Turned on Automatic user confirmation setting" + }, + "autoConfirmDisabledByAdmin": { + "message": "Turned off Automatic user confirmation setting" + }, + "autoConfirmEnabledByPortal": { + "message": "Added Automatic user confirmation policy" + }, + "autoConfirmDisabledByPortal": { + "message": "Removed Automatic user confirmation policy" + }, + "system": { + "message": "System" + }, "personalOwnership": { "message": "Remove individual vault" }, @@ -7400,6 +7417,9 @@ "invalidVerificationCode": { "message": "Invalid verification code" }, + "invalidEmailOrVerificationCode": { + "message": "Invalid email or verification code" + }, "keyConnectorDomain": { "message": "Key Connector domain" }, @@ -12857,6 +12877,21 @@ "storageFullDescription": { "message": "You have used all $GB$ GB of your encrypted storage. To continue storing files, add more storage." }, + "extensionPromptHeading": { + "message": "Get the extension for easy vault access" + }, + "extensionPromptBody": { + "message": "With the browser extension installed, you'll take Bitwarden everywhere online. It'll fill in passwords, so you can log into your accounts with a single click." + }, + "extensionPromptImageAlt": { + "message": "A web browser showing the Bitwarden extension with autofill items for the current webpage." + }, + "skip": { + "message": "Skip" + }, + "downloadExtension": { + "message": "Download extension" + }, "whoCanView": { "message": "Who can view" }, @@ -12869,6 +12904,9 @@ "enterMultipleEmailsSeparatedByComma": { "message": "Enter multiple emails by separating with a comma." }, + "emailsRequiredChangeAccessType": { + "message": "Email verification requires at least one email address. To remove all emails, change the access type above." + }, "emailPlaceholder": { "message": "user@bitwarden.com , user@acme.com" }, @@ -12893,6 +12931,18 @@ "invalidSendPassword": { "message": "Invalid Send password" }, + "vaultWelcomeDialogTitle": { + "message": "You're in! Welcome to Bitwarden" + }, + "vaultWelcomeDialogDescription": { + "message": "Store all your passwords and personal info in your Bitwarden vault. We'll show you around." + }, + "vaultWelcomeDialogPrimaryCta": { + "message": "Start tour" + }, + "vaultWelcomeDialogDismissCta": { + "message": "Skip" + }, "sendPasswordHelperText": { "message": "Individuals will need to enter the password to view this Send", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." @@ -12948,5 +12998,23 @@ }, "paymentMethodUpdateError": { "message": "There was an error updating your payment method." + }, + "sendPasswordInvalidAskOwner": { + "message": "Invalid password. Ask the sender for the password needed to access this Send.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "sendExpiresOn": { + "message": "This Send expires at $TIME$ on $DATE$", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated.", + "placeholders": { + "time": { + "content": "$1", + "example": "10:00 AM" + }, + "date": { + "content": "$2", + "example": "Jan 1, 1970" + } + } } } diff --git a/apps/web/src/locales/nb/messages.json b/apps/web/src/locales/nb/messages.json index de97b70a119..6c463a61e64 100644 --- a/apps/web/src/locales/nb/messages.json +++ b/apps/web/src/locales/nb/messages.json @@ -4337,6 +4337,15 @@ } } }, + "automaticallyConfirmedUserId": { + "message": "Automatically confirmed user $ID$.", + "placeholders": { + "id": { + "content": "$1", + "example": "John Smith" + } + } + }, "editedUserId": { "message": "Redigerte brukeren $ID$.", "placeholders": { @@ -4596,29 +4605,26 @@ "generatingYourAccessIntelligence": { "message": "Generating your Access Intelligence..." }, - "fetchingMemberData": { - "message": "Fetching member data..." - }, - "analyzingPasswordHealth": { - "message": "Analyzing password health..." - }, - "calculatingRiskScores": { - "message": "Calculating risk scores..." - }, - "generatingReportData": { - "message": "Generating report data..." - }, - "savingReport": { - "message": "Saving report..." - }, - "compilingInsights": { - "message": "Compiling insights..." - }, "loadingProgress": { "message": "Loading progress" }, - "thisMightTakeFewMinutes": { - "message": "This might take a few minutes." + "reviewingMemberData": { + "message": "Reviewing member data..." + }, + "analyzingPasswords": { + "message": "Analyzing passwords..." + }, + "calculatingRisks": { + "message": "Calculating risks..." + }, + "generatingReports": { + "message": "Generating reports..." + }, + "compilingInsightsProgress": { + "message": "Compiling insights..." + }, + "reportGenerationDone": { + "message": "Done!" }, "riskInsightsRunReport": { "message": "Run report" @@ -5849,10 +5855,6 @@ "message": "Vet du ikke passordet? Be avsender om nødvendig tilgang til denne Send.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, - "sendHiddenByDefault": { - "message": "Denne Send-en er skjult som standard. Du kan veksle synlighet ved å bruke knappen nedenfor.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, "downloadAttachments": { "message": "Download attachments" }, @@ -6145,6 +6147,21 @@ "autoConfirmCheckBoxLabel": { "message": "I accept these risks and policy updates" }, + "autoConfirmEnabledByAdmin": { + "message": "Turned on Automatic user confirmation setting" + }, + "autoConfirmDisabledByAdmin": { + "message": "Turned off Automatic user confirmation setting" + }, + "autoConfirmEnabledByPortal": { + "message": "Added Automatic user confirmation policy" + }, + "autoConfirmDisabledByPortal": { + "message": "Removed Automatic user confirmation policy" + }, + "system": { + "message": "System" + }, "personalOwnership": { "message": "Personlig eierskap" }, @@ -7400,6 +7417,9 @@ "invalidVerificationCode": { "message": "Ugyldig bekreftelseskode" }, + "invalidEmailOrVerificationCode": { + "message": "Invalid email or verification code" + }, "keyConnectorDomain": { "message": "Key Connector domain" }, @@ -12857,6 +12877,21 @@ "storageFullDescription": { "message": "You have used all $GB$ GB of your encrypted storage. To continue storing files, add more storage." }, + "extensionPromptHeading": { + "message": "Get the extension for easy vault access" + }, + "extensionPromptBody": { + "message": "With the browser extension installed, you'll take Bitwarden everywhere online. It'll fill in passwords, so you can log into your accounts with a single click." + }, + "extensionPromptImageAlt": { + "message": "A web browser showing the Bitwarden extension with autofill items for the current webpage." + }, + "skip": { + "message": "Skip" + }, + "downloadExtension": { + "message": "Download extension" + }, "whoCanView": { "message": "Who can view" }, @@ -12869,6 +12904,9 @@ "enterMultipleEmailsSeparatedByComma": { "message": "Enter multiple emails by separating with a comma." }, + "emailsRequiredChangeAccessType": { + "message": "Email verification requires at least one email address. To remove all emails, change the access type above." + }, "emailPlaceholder": { "message": "user@bitwarden.com , user@acme.com" }, @@ -12893,6 +12931,18 @@ "invalidSendPassword": { "message": "Invalid Send password" }, + "vaultWelcomeDialogTitle": { + "message": "You're in! Welcome to Bitwarden" + }, + "vaultWelcomeDialogDescription": { + "message": "Store all your passwords and personal info in your Bitwarden vault. We'll show you around." + }, + "vaultWelcomeDialogPrimaryCta": { + "message": "Start tour" + }, + "vaultWelcomeDialogDismissCta": { + "message": "Skip" + }, "sendPasswordHelperText": { "message": "Individuals will need to enter the password to view this Send", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." @@ -12948,5 +12998,23 @@ }, "paymentMethodUpdateError": { "message": "There was an error updating your payment method." + }, + "sendPasswordInvalidAskOwner": { + "message": "Invalid password. Ask the sender for the password needed to access this Send.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "sendExpiresOn": { + "message": "This Send expires at $TIME$ on $DATE$", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated.", + "placeholders": { + "time": { + "content": "$1", + "example": "10:00 AM" + }, + "date": { + "content": "$2", + "example": "Jan 1, 1970" + } + } } } diff --git a/apps/web/src/locales/ne/messages.json b/apps/web/src/locales/ne/messages.json index 26c942795b6..9ef82cc799c 100644 --- a/apps/web/src/locales/ne/messages.json +++ b/apps/web/src/locales/ne/messages.json @@ -4337,6 +4337,15 @@ } } }, + "automaticallyConfirmedUserId": { + "message": "Automatically confirmed user $ID$.", + "placeholders": { + "id": { + "content": "$1", + "example": "John Smith" + } + } + }, "editedUserId": { "message": "Edited user $ID$.", "placeholders": { @@ -4596,29 +4605,26 @@ "generatingYourAccessIntelligence": { "message": "Generating your Access Intelligence..." }, - "fetchingMemberData": { - "message": "Fetching member data..." - }, - "analyzingPasswordHealth": { - "message": "Analyzing password health..." - }, - "calculatingRiskScores": { - "message": "Calculating risk scores..." - }, - "generatingReportData": { - "message": "Generating report data..." - }, - "savingReport": { - "message": "Saving report..." - }, - "compilingInsights": { - "message": "Compiling insights..." - }, "loadingProgress": { "message": "Loading progress" }, - "thisMightTakeFewMinutes": { - "message": "This might take a few minutes." + "reviewingMemberData": { + "message": "Reviewing member data..." + }, + "analyzingPasswords": { + "message": "Analyzing passwords..." + }, + "calculatingRisks": { + "message": "Calculating risks..." + }, + "generatingReports": { + "message": "Generating reports..." + }, + "compilingInsightsProgress": { + "message": "Compiling insights..." + }, + "reportGenerationDone": { + "message": "Done!" }, "riskInsightsRunReport": { "message": "Run report" @@ -5849,10 +5855,6 @@ "message": "Don't know the password? Ask the sender for the password needed to access this Send.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, - "sendHiddenByDefault": { - "message": "This Send is hidden by default. You can toggle its visibility using the button below.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, "downloadAttachments": { "message": "Download attachments" }, @@ -6145,6 +6147,21 @@ "autoConfirmCheckBoxLabel": { "message": "I accept these risks and policy updates" }, + "autoConfirmEnabledByAdmin": { + "message": "Turned on Automatic user confirmation setting" + }, + "autoConfirmDisabledByAdmin": { + "message": "Turned off Automatic user confirmation setting" + }, + "autoConfirmEnabledByPortal": { + "message": "Added Automatic user confirmation policy" + }, + "autoConfirmDisabledByPortal": { + "message": "Removed Automatic user confirmation policy" + }, + "system": { + "message": "System" + }, "personalOwnership": { "message": "Remove individual vault" }, @@ -7400,6 +7417,9 @@ "invalidVerificationCode": { "message": "Invalid verification code" }, + "invalidEmailOrVerificationCode": { + "message": "Invalid email or verification code" + }, "keyConnectorDomain": { "message": "Key Connector domain" }, @@ -12857,6 +12877,21 @@ "storageFullDescription": { "message": "You have used all $GB$ GB of your encrypted storage. To continue storing files, add more storage." }, + "extensionPromptHeading": { + "message": "Get the extension for easy vault access" + }, + "extensionPromptBody": { + "message": "With the browser extension installed, you'll take Bitwarden everywhere online. It'll fill in passwords, so you can log into your accounts with a single click." + }, + "extensionPromptImageAlt": { + "message": "A web browser showing the Bitwarden extension with autofill items for the current webpage." + }, + "skip": { + "message": "Skip" + }, + "downloadExtension": { + "message": "Download extension" + }, "whoCanView": { "message": "Who can view" }, @@ -12869,6 +12904,9 @@ "enterMultipleEmailsSeparatedByComma": { "message": "Enter multiple emails by separating with a comma." }, + "emailsRequiredChangeAccessType": { + "message": "Email verification requires at least one email address. To remove all emails, change the access type above." + }, "emailPlaceholder": { "message": "user@bitwarden.com , user@acme.com" }, @@ -12893,6 +12931,18 @@ "invalidSendPassword": { "message": "Invalid Send password" }, + "vaultWelcomeDialogTitle": { + "message": "You're in! Welcome to Bitwarden" + }, + "vaultWelcomeDialogDescription": { + "message": "Store all your passwords and personal info in your Bitwarden vault. We'll show you around." + }, + "vaultWelcomeDialogPrimaryCta": { + "message": "Start tour" + }, + "vaultWelcomeDialogDismissCta": { + "message": "Skip" + }, "sendPasswordHelperText": { "message": "Individuals will need to enter the password to view this Send", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." @@ -12948,5 +12998,23 @@ }, "paymentMethodUpdateError": { "message": "There was an error updating your payment method." + }, + "sendPasswordInvalidAskOwner": { + "message": "Invalid password. Ask the sender for the password needed to access this Send.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "sendExpiresOn": { + "message": "This Send expires at $TIME$ on $DATE$", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated.", + "placeholders": { + "time": { + "content": "$1", + "example": "10:00 AM" + }, + "date": { + "content": "$2", + "example": "Jan 1, 1970" + } + } } } diff --git a/apps/web/src/locales/nl/messages.json b/apps/web/src/locales/nl/messages.json index 2748009dba8..e50966e3ca1 100644 --- a/apps/web/src/locales/nl/messages.json +++ b/apps/web/src/locales/nl/messages.json @@ -4337,6 +4337,15 @@ } } }, + "automaticallyConfirmedUserId": { + "message": "Automatisch bevestigde gebruiker $ID$.", + "placeholders": { + "id": { + "content": "$1", + "example": "John Smith" + } + } + }, "editedUserId": { "message": "Gebruiker gewijzigd $ID$.", "placeholders": { @@ -4596,29 +4605,26 @@ "generatingYourAccessIntelligence": { "message": "Je toegangsinlichtingen genereren..." }, - "fetchingMemberData": { - "message": "Ledengegevens ophalen..." - }, - "analyzingPasswordHealth": { - "message": "Wachtwoordgezondheid analyseren..." - }, - "calculatingRiskScores": { - "message": "Risicoscores berekenen..." - }, - "generatingReportData": { - "message": "Rapportgegevens genereren..." - }, - "savingReport": { - "message": "Rapport opslaan..." - }, - "compilingInsights": { - "message": "Inzichten compileren..." - }, "loadingProgress": { "message": "Voortgang laden" }, - "thisMightTakeFewMinutes": { - "message": "Dit kan een paar minuten duren." + "reviewingMemberData": { + "message": "Ledengegevens controleren..." + }, + "analyzingPasswords": { + "message": "Wachtwoorden analyseren..." + }, + "calculatingRisks": { + "message": "Risicoscores berekenen..." + }, + "generatingReports": { + "message": "Rapporteren genereren..." + }, + "compilingInsightsProgress": { + "message": "Inzichten compileren..." + }, + "reportGenerationDone": { + "message": "Klaar!" }, "riskInsightsRunReport": { "message": "Rapport uitvoeren" @@ -5849,10 +5855,6 @@ "message": "Weet je het wachtwoord niet? Vraag de afzender om het wachtwoord om toegang te krijgen tot deze Send.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, - "sendHiddenByDefault": { - "message": "Deze Send is standaard verborgen. Je kunt de zichtbaarheid ervan in- en uitschakelen met de knop hieronder.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, "downloadAttachments": { "message": "Bijlagen downloaden" }, @@ -6145,6 +6147,21 @@ "autoConfirmCheckBoxLabel": { "message": "Ik accepteer deze risico's en beleidsupdates" }, + "autoConfirmEnabledByAdmin": { + "message": "Automatisch gebruikersbevestigingsinstelling ingeschakeld" + }, + "autoConfirmDisabledByAdmin": { + "message": "Automatisch gebruikersbevestigingsinstelling uitgeschakeld" + }, + "autoConfirmEnabledByPortal": { + "message": "Automatisch gebruikersbevestigingsbeleid toegevoegd" + }, + "autoConfirmDisabledByPortal": { + "message": "Automatisch gebruikersbevestigingsbeleid verwijderd" + }, + "system": { + "message": "Systeem" + }, "personalOwnership": { "message": "Persoonlijk eigendom" }, @@ -7400,6 +7417,9 @@ "invalidVerificationCode": { "message": "Ongeldige verificatiecode" }, + "invalidEmailOrVerificationCode": { + "message": "Ongeldig e-mailadres verificatiecode" + }, "keyConnectorDomain": { "message": "Key Connector-domein" }, @@ -12857,6 +12877,21 @@ "storageFullDescription": { "message": "Je hebt alle $GB$ GB aan versleutelde opslag gebruikt. Voeg meer opslagruimte toe om door te gaan met het opslaan van bestanden." }, + "extensionPromptHeading": { + "message": "Gebruik de extensie voor eenvoudige toegang tot je kluis" + }, + "extensionPromptBody": { + "message": "Met de browserextensie kun je Bitwarden overal online gebruiken. Het invullen van wachtwoorden, zodat je met één klik op je accounts kunt inloggen." + }, + "extensionPromptImageAlt": { + "message": "Een webbrowser die de Bitwarden-extensie toont met automatisch invullen voor de huidige webpagina." + }, + "skip": { + "message": "Overslaan" + }, + "downloadExtension": { + "message": "Extensie downloaden" + }, "whoCanView": { "message": "Wie kan weergeven" }, @@ -12869,6 +12904,9 @@ "enterMultipleEmailsSeparatedByComma": { "message": "Voer meerdere e-mailadressen in door te scheiden met een komma." }, + "emailsRequiredChangeAccessType": { + "message": "E-mailverificatie vereist ten minste één e-mailadres. Om alle e-mailadressen te verwijderen, moet je het toegangstype hierboven wijzigen." + }, "emailPlaceholder": { "message": "user@bitwarden.com , user@acme.com" }, @@ -12893,6 +12931,18 @@ "invalidSendPassword": { "message": "Ongeldig Send-wachtwoord" }, + "vaultWelcomeDialogTitle": { + "message": "Je bent erbij! Welkom bij Bitwarden" + }, + "vaultWelcomeDialogDescription": { + "message": "Sla al je wachtwoorden en persoonlijke informatie op in je Bitwarden-kluis. We laten je zien hoe het werkt." + }, + "vaultWelcomeDialogPrimaryCta": { + "message": "Rondleiding starten" + }, + "vaultWelcomeDialogDismissCta": { + "message": "Overslaan" + }, "sendPasswordHelperText": { "message": "Individuen moeten het wachtwoord invoeren om deze Send te bekijken", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." @@ -12948,5 +12998,23 @@ }, "paymentMethodUpdateError": { "message": "Er is een fout opgetreden bij het bijwerken van je betaalmethode." + }, + "sendPasswordInvalidAskOwner": { + "message": "Onjuist wachtwoord. Vraag de afzender om het wachtwoord om toegang te krijgen tot deze Send.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "sendExpiresOn": { + "message": "Deze Send verloopt om $TIME$ op $DATE$", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated.", + "placeholders": { + "time": { + "content": "$1", + "example": "10:00 AM" + }, + "date": { + "content": "$2", + "example": "Jan 1, 1970" + } + } } } diff --git a/apps/web/src/locales/nn/messages.json b/apps/web/src/locales/nn/messages.json index 78a638ee05a..f87cf97aa65 100644 --- a/apps/web/src/locales/nn/messages.json +++ b/apps/web/src/locales/nn/messages.json @@ -4337,6 +4337,15 @@ } } }, + "automaticallyConfirmedUserId": { + "message": "Automatically confirmed user $ID$.", + "placeholders": { + "id": { + "content": "$1", + "example": "John Smith" + } + } + }, "editedUserId": { "message": "Edited user $ID$.", "placeholders": { @@ -4596,29 +4605,26 @@ "generatingYourAccessIntelligence": { "message": "Generating your Access Intelligence..." }, - "fetchingMemberData": { - "message": "Fetching member data..." - }, - "analyzingPasswordHealth": { - "message": "Analyzing password health..." - }, - "calculatingRiskScores": { - "message": "Calculating risk scores..." - }, - "generatingReportData": { - "message": "Generating report data..." - }, - "savingReport": { - "message": "Saving report..." - }, - "compilingInsights": { - "message": "Compiling insights..." - }, "loadingProgress": { "message": "Loading progress" }, - "thisMightTakeFewMinutes": { - "message": "This might take a few minutes." + "reviewingMemberData": { + "message": "Reviewing member data..." + }, + "analyzingPasswords": { + "message": "Analyzing passwords..." + }, + "calculatingRisks": { + "message": "Calculating risks..." + }, + "generatingReports": { + "message": "Generating reports..." + }, + "compilingInsightsProgress": { + "message": "Compiling insights..." + }, + "reportGenerationDone": { + "message": "Done!" }, "riskInsightsRunReport": { "message": "Run report" @@ -5849,10 +5855,6 @@ "message": "Don't know the password? Ask the sender for the password needed to access this Send.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, - "sendHiddenByDefault": { - "message": "This Send is hidden by default. You can toggle its visibility using the button below.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, "downloadAttachments": { "message": "Download attachments" }, @@ -6145,6 +6147,21 @@ "autoConfirmCheckBoxLabel": { "message": "I accept these risks and policy updates" }, + "autoConfirmEnabledByAdmin": { + "message": "Turned on Automatic user confirmation setting" + }, + "autoConfirmDisabledByAdmin": { + "message": "Turned off Automatic user confirmation setting" + }, + "autoConfirmEnabledByPortal": { + "message": "Added Automatic user confirmation policy" + }, + "autoConfirmDisabledByPortal": { + "message": "Removed Automatic user confirmation policy" + }, + "system": { + "message": "System" + }, "personalOwnership": { "message": "Remove individual vault" }, @@ -7400,6 +7417,9 @@ "invalidVerificationCode": { "message": "Invalid verification code" }, + "invalidEmailOrVerificationCode": { + "message": "Invalid email or verification code" + }, "keyConnectorDomain": { "message": "Key Connector domain" }, @@ -12857,6 +12877,21 @@ "storageFullDescription": { "message": "You have used all $GB$ GB of your encrypted storage. To continue storing files, add more storage." }, + "extensionPromptHeading": { + "message": "Get the extension for easy vault access" + }, + "extensionPromptBody": { + "message": "With the browser extension installed, you'll take Bitwarden everywhere online. It'll fill in passwords, so you can log into your accounts with a single click." + }, + "extensionPromptImageAlt": { + "message": "A web browser showing the Bitwarden extension with autofill items for the current webpage." + }, + "skip": { + "message": "Skip" + }, + "downloadExtension": { + "message": "Download extension" + }, "whoCanView": { "message": "Who can view" }, @@ -12869,6 +12904,9 @@ "enterMultipleEmailsSeparatedByComma": { "message": "Enter multiple emails by separating with a comma." }, + "emailsRequiredChangeAccessType": { + "message": "Email verification requires at least one email address. To remove all emails, change the access type above." + }, "emailPlaceholder": { "message": "user@bitwarden.com , user@acme.com" }, @@ -12893,6 +12931,18 @@ "invalidSendPassword": { "message": "Invalid Send password" }, + "vaultWelcomeDialogTitle": { + "message": "You're in! Welcome to Bitwarden" + }, + "vaultWelcomeDialogDescription": { + "message": "Store all your passwords and personal info in your Bitwarden vault. We'll show you around." + }, + "vaultWelcomeDialogPrimaryCta": { + "message": "Start tour" + }, + "vaultWelcomeDialogDismissCta": { + "message": "Skip" + }, "sendPasswordHelperText": { "message": "Individuals will need to enter the password to view this Send", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." @@ -12948,5 +12998,23 @@ }, "paymentMethodUpdateError": { "message": "There was an error updating your payment method." + }, + "sendPasswordInvalidAskOwner": { + "message": "Invalid password. Ask the sender for the password needed to access this Send.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "sendExpiresOn": { + "message": "This Send expires at $TIME$ on $DATE$", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated.", + "placeholders": { + "time": { + "content": "$1", + "example": "10:00 AM" + }, + "date": { + "content": "$2", + "example": "Jan 1, 1970" + } + } } } diff --git a/apps/web/src/locales/or/messages.json b/apps/web/src/locales/or/messages.json index bc0e3b74cb6..4e6a8265ad6 100644 --- a/apps/web/src/locales/or/messages.json +++ b/apps/web/src/locales/or/messages.json @@ -4337,6 +4337,15 @@ } } }, + "automaticallyConfirmedUserId": { + "message": "Automatically confirmed user $ID$.", + "placeholders": { + "id": { + "content": "$1", + "example": "John Smith" + } + } + }, "editedUserId": { "message": "Edited user $ID$.", "placeholders": { @@ -4596,29 +4605,26 @@ "generatingYourAccessIntelligence": { "message": "Generating your Access Intelligence..." }, - "fetchingMemberData": { - "message": "Fetching member data..." - }, - "analyzingPasswordHealth": { - "message": "Analyzing password health..." - }, - "calculatingRiskScores": { - "message": "Calculating risk scores..." - }, - "generatingReportData": { - "message": "Generating report data..." - }, - "savingReport": { - "message": "Saving report..." - }, - "compilingInsights": { - "message": "Compiling insights..." - }, "loadingProgress": { "message": "Loading progress" }, - "thisMightTakeFewMinutes": { - "message": "This might take a few minutes." + "reviewingMemberData": { + "message": "Reviewing member data..." + }, + "analyzingPasswords": { + "message": "Analyzing passwords..." + }, + "calculatingRisks": { + "message": "Calculating risks..." + }, + "generatingReports": { + "message": "Generating reports..." + }, + "compilingInsightsProgress": { + "message": "Compiling insights..." + }, + "reportGenerationDone": { + "message": "Done!" }, "riskInsightsRunReport": { "message": "Run report" @@ -5849,10 +5855,6 @@ "message": "Don't know the password? Ask the sender for the password needed to access this Send.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, - "sendHiddenByDefault": { - "message": "This Send is hidden by default. You can toggle its visibility using the button below.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, "downloadAttachments": { "message": "Download attachments" }, @@ -6145,6 +6147,21 @@ "autoConfirmCheckBoxLabel": { "message": "I accept these risks and policy updates" }, + "autoConfirmEnabledByAdmin": { + "message": "Turned on Automatic user confirmation setting" + }, + "autoConfirmDisabledByAdmin": { + "message": "Turned off Automatic user confirmation setting" + }, + "autoConfirmEnabledByPortal": { + "message": "Added Automatic user confirmation policy" + }, + "autoConfirmDisabledByPortal": { + "message": "Removed Automatic user confirmation policy" + }, + "system": { + "message": "System" + }, "personalOwnership": { "message": "Remove individual vault" }, @@ -7400,6 +7417,9 @@ "invalidVerificationCode": { "message": "Invalid verification code" }, + "invalidEmailOrVerificationCode": { + "message": "Invalid email or verification code" + }, "keyConnectorDomain": { "message": "Key Connector domain" }, @@ -12857,6 +12877,21 @@ "storageFullDescription": { "message": "You have used all $GB$ GB of your encrypted storage. To continue storing files, add more storage." }, + "extensionPromptHeading": { + "message": "Get the extension for easy vault access" + }, + "extensionPromptBody": { + "message": "With the browser extension installed, you'll take Bitwarden everywhere online. It'll fill in passwords, so you can log into your accounts with a single click." + }, + "extensionPromptImageAlt": { + "message": "A web browser showing the Bitwarden extension with autofill items for the current webpage." + }, + "skip": { + "message": "Skip" + }, + "downloadExtension": { + "message": "Download extension" + }, "whoCanView": { "message": "Who can view" }, @@ -12869,6 +12904,9 @@ "enterMultipleEmailsSeparatedByComma": { "message": "Enter multiple emails by separating with a comma." }, + "emailsRequiredChangeAccessType": { + "message": "Email verification requires at least one email address. To remove all emails, change the access type above." + }, "emailPlaceholder": { "message": "user@bitwarden.com , user@acme.com" }, @@ -12893,6 +12931,18 @@ "invalidSendPassword": { "message": "Invalid Send password" }, + "vaultWelcomeDialogTitle": { + "message": "You're in! Welcome to Bitwarden" + }, + "vaultWelcomeDialogDescription": { + "message": "Store all your passwords and personal info in your Bitwarden vault. We'll show you around." + }, + "vaultWelcomeDialogPrimaryCta": { + "message": "Start tour" + }, + "vaultWelcomeDialogDismissCta": { + "message": "Skip" + }, "sendPasswordHelperText": { "message": "Individuals will need to enter the password to view this Send", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." @@ -12948,5 +12998,23 @@ }, "paymentMethodUpdateError": { "message": "There was an error updating your payment method." + }, + "sendPasswordInvalidAskOwner": { + "message": "Invalid password. Ask the sender for the password needed to access this Send.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "sendExpiresOn": { + "message": "This Send expires at $TIME$ on $DATE$", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated.", + "placeholders": { + "time": { + "content": "$1", + "example": "10:00 AM" + }, + "date": { + "content": "$2", + "example": "Jan 1, 1970" + } + } } } diff --git a/apps/web/src/locales/pl/messages.json b/apps/web/src/locales/pl/messages.json index 40ba151b7ad..82f18f25c31 100644 --- a/apps/web/src/locales/pl/messages.json +++ b/apps/web/src/locales/pl/messages.json @@ -4337,6 +4337,15 @@ } } }, + "automaticallyConfirmedUserId": { + "message": "Automatically confirmed user $ID$.", + "placeholders": { + "id": { + "content": "$1", + "example": "John Smith" + } + } + }, "editedUserId": { "message": "Użytkownik $ID$ został zaktualizowany.", "placeholders": { @@ -4596,29 +4605,26 @@ "generatingYourAccessIntelligence": { "message": "Generating your Access Intelligence..." }, - "fetchingMemberData": { - "message": "Fetching member data..." - }, - "analyzingPasswordHealth": { - "message": "Analyzing password health..." - }, - "calculatingRiskScores": { - "message": "Calculating risk scores..." - }, - "generatingReportData": { - "message": "Generating report data..." - }, - "savingReport": { - "message": "Saving report..." - }, - "compilingInsights": { - "message": "Compiling insights..." - }, "loadingProgress": { "message": "Loading progress" }, - "thisMightTakeFewMinutes": { - "message": "This might take a few minutes." + "reviewingMemberData": { + "message": "Reviewing member data..." + }, + "analyzingPasswords": { + "message": "Analyzing passwords..." + }, + "calculatingRisks": { + "message": "Calculating risks..." + }, + "generatingReports": { + "message": "Generating reports..." + }, + "compilingInsightsProgress": { + "message": "Compiling insights..." + }, + "reportGenerationDone": { + "message": "Done!" }, "riskInsightsRunReport": { "message": "Run report" @@ -5849,10 +5855,6 @@ "message": "Nie znasz hasła? Poproś nadawcę o hasło, aby uzyskać dostęp do wysyłki.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, - "sendHiddenByDefault": { - "message": "Ta wysyłka jest domyślnie ukryta. Możesz zmienić jej widoczność za pomocą przycisku.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, "downloadAttachments": { "message": "Pobierz załączniki" }, @@ -6145,6 +6147,21 @@ "autoConfirmCheckBoxLabel": { "message": "I accept these risks and policy updates" }, + "autoConfirmEnabledByAdmin": { + "message": "Turned on Automatic user confirmation setting" + }, + "autoConfirmDisabledByAdmin": { + "message": "Turned off Automatic user confirmation setting" + }, + "autoConfirmEnabledByPortal": { + "message": "Added Automatic user confirmation policy" + }, + "autoConfirmDisabledByPortal": { + "message": "Removed Automatic user confirmation policy" + }, + "system": { + "message": "System" + }, "personalOwnership": { "message": "Własność osobista" }, @@ -7400,6 +7417,9 @@ "invalidVerificationCode": { "message": "Kod weryfikacyjny jest nieprawidłowy" }, + "invalidEmailOrVerificationCode": { + "message": "Invalid email or verification code" + }, "keyConnectorDomain": { "message": "Domena Key Connector'a" }, @@ -12857,6 +12877,21 @@ "storageFullDescription": { "message": "You have used all $GB$ GB of your encrypted storage. To continue storing files, add more storage." }, + "extensionPromptHeading": { + "message": "Get the extension for easy vault access" + }, + "extensionPromptBody": { + "message": "With the browser extension installed, you'll take Bitwarden everywhere online. It'll fill in passwords, so you can log into your accounts with a single click." + }, + "extensionPromptImageAlt": { + "message": "A web browser showing the Bitwarden extension with autofill items for the current webpage." + }, + "skip": { + "message": "Skip" + }, + "downloadExtension": { + "message": "Download extension" + }, "whoCanView": { "message": "Who can view" }, @@ -12869,6 +12904,9 @@ "enterMultipleEmailsSeparatedByComma": { "message": "Enter multiple emails by separating with a comma." }, + "emailsRequiredChangeAccessType": { + "message": "Email verification requires at least one email address. To remove all emails, change the access type above." + }, "emailPlaceholder": { "message": "user@bitwarden.com , user@acme.com" }, @@ -12893,6 +12931,18 @@ "invalidSendPassword": { "message": "Invalid Send password" }, + "vaultWelcomeDialogTitle": { + "message": "You're in! Welcome to Bitwarden" + }, + "vaultWelcomeDialogDescription": { + "message": "Store all your passwords and personal info in your Bitwarden vault. We'll show you around." + }, + "vaultWelcomeDialogPrimaryCta": { + "message": "Start tour" + }, + "vaultWelcomeDialogDismissCta": { + "message": "Skip" + }, "sendPasswordHelperText": { "message": "Individuals will need to enter the password to view this Send", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." @@ -12948,5 +12998,23 @@ }, "paymentMethodUpdateError": { "message": "There was an error updating your payment method." + }, + "sendPasswordInvalidAskOwner": { + "message": "Invalid password. Ask the sender for the password needed to access this Send.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "sendExpiresOn": { + "message": "This Send expires at $TIME$ on $DATE$", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated.", + "placeholders": { + "time": { + "content": "$1", + "example": "10:00 AM" + }, + "date": { + "content": "$2", + "example": "Jan 1, 1970" + } + } } } diff --git a/apps/web/src/locales/pt_BR/messages.json b/apps/web/src/locales/pt_BR/messages.json index f78d39715cc..73b3994fab3 100644 --- a/apps/web/src/locales/pt_BR/messages.json +++ b/apps/web/src/locales/pt_BR/messages.json @@ -15,7 +15,7 @@ "message": "Nenhum aplicativo crítico em risco" }, "critical": { - "message": "Critical ($COUNT$)", + "message": "Críticos ($COUNT$)", "placeholders": { "count": { "content": "$1", @@ -24,7 +24,7 @@ } }, "notCritical": { - "message": "Not critical ($COUNT$)", + "message": "Não críticos ($COUNT$)", "placeholders": { "count": { "content": "$1", @@ -33,13 +33,13 @@ } }, "criticalBadge": { - "message": "Critical" + "message": "Crítico" }, "accessIntelligence": { "message": "Inteligência de acesso" }, "noApplicationsMatchTheseFilters": { - "message": "No applications match these filters" + "message": "Nenhum aplicativo corresponde aos filtros" }, "passwordRisk": { "message": "Risco de senhas" @@ -48,7 +48,7 @@ "message": "Você não tem permissão para editar este item" }, "reviewAccessIntelligence": { - "message": "Review security reports to find and fix credential risks before they escalate." + "message": "Revise os relatórios de segurança para encontrar e corrigir riscos antes que cresçam." }, "reviewAtRiskLoginsPrompt": { "message": "Revisar credenciais em risco" @@ -269,7 +269,7 @@ } }, "numCriticalApplicationsMarkedSuccess": { - "message": "$COUNT$ applications marked critical", + "message": "$COUNT$ aplicativos marcados como críticos", "placeholders": { "count": { "content": "$1", @@ -278,7 +278,7 @@ } }, "numApplicationsUnmarkedCriticalSuccess": { - "message": "$COUNT$ applications marked not critical", + "message": "$COUNT$ aplicativos marcados como não críticos", "placeholders": { "count": { "content": "$1", @@ -287,7 +287,7 @@ } }, "markAppCountAsCritical": { - "message": "Mark $COUNT$ as critical", + "message": "Marcar $COUNT$ como críticos", "placeholders": { "count": { "content": "$1", @@ -296,7 +296,7 @@ } }, "markAppCountAsNotCritical": { - "message": "Mark $COUNT$ as not critical", + "message": "Marcar $COUNT$ como não críticos", "placeholders": { "count": { "content": "$1", @@ -311,7 +311,7 @@ "message": "Aplicativo" }, "applications": { - "message": "Applications" + "message": "Aplicativos" }, "atRiskPasswords": { "message": "Senhas em risco" @@ -650,7 +650,7 @@ "message": "E-mail" }, "emails": { - "message": "Emails" + "message": "E-mails" }, "phone": { "message": "Telefone" @@ -1284,7 +1284,7 @@ "message": "Selecionar tudo" }, "deselectAll": { - "message": "Deselect all" + "message": "Desselecionar tudo" }, "unselectAll": { "message": "Deselecionar tudo" @@ -1435,10 +1435,10 @@ "message": "Não" }, "noAuth": { - "message": "Anyone with the link" + "message": "Qualquer pessoa com o link" }, "anyOneWithPassword": { - "message": "Anyone with a password set by you" + "message": "Qualquer pessoa com uma senha configurada por você" }, "location": { "message": "Localização" @@ -3339,13 +3339,13 @@ "message": "A assinatura foi restabelecida." }, "resubscribe": { - "message": "Resubscribe" + "message": "Reinscrever-se" }, "yourSubscriptionIsExpired": { - "message": "Your subscription is expired" + "message": "Sua assinatura expirou" }, "yourSubscriptionIsCanceled": { - "message": "Your subscription is canceled" + "message": "Sua assinatura foi cancelada" }, "cancelConfirmation": { "message": "Você tem certeza que deseja cancelar? Você perderá o acesso a todos os recursos dessa assinatura no final deste ciclo de faturamento." @@ -3366,7 +3366,7 @@ "message": "Próxima cobrança" }, "nextChargeDate": { - "message": "Next charge date" + "message": "Próxima cobrança" }, "plan": { "message": "Plano" @@ -3857,7 +3857,7 @@ "message": "Editar conjunto" }, "viewCollection": { - "message": "View collection" + "message": "Ver conjunto" }, "collectionInfo": { "message": "Informações do conjunto" @@ -4337,6 +4337,15 @@ } } }, + "automaticallyConfirmedUserId": { + "message": "Usuário $ID$ foi confirmado automaticamente.", + "placeholders": { + "id": { + "content": "$1", + "example": "John Smith" + } + } + }, "editedUserId": { "message": "Editou o usuário $ID$.", "placeholders": { @@ -4596,29 +4605,26 @@ "generatingYourAccessIntelligence": { "message": "Gerando sua Inteligência de Acesso..." }, - "fetchingMemberData": { - "message": "Buscando dados de membros..." - }, - "analyzingPasswordHealth": { - "message": "Analisando saúde das senhas..." - }, - "calculatingRiskScores": { - "message": "Calculando pontuações de risco..." - }, - "generatingReportData": { - "message": "Gerando dados do relatório..." - }, - "savingReport": { - "message": "Salvando relatório..." - }, - "compilingInsights": { - "message": "Compilando conhecimentos..." - }, "loadingProgress": { "message": "Progresso de carregamento" }, - "thisMightTakeFewMinutes": { - "message": "Isto pode levar alguns minutos." + "reviewingMemberData": { + "message": "Revisando os dados dos membros..." + }, + "analyzingPasswords": { + "message": "Analisando as senhas..." + }, + "calculatingRisks": { + "message": "Calculando os riscos..." + }, + "generatingReports": { + "message": "Gerando os relatórios..." + }, + "compilingInsightsProgress": { + "message": "Compilando conhecimentos..." + }, + "reportGenerationDone": { + "message": "Pronto!" }, "riskInsightsRunReport": { "message": "Executar relatório" @@ -5440,7 +5446,7 @@ "message": "Número mínimo de palavras" }, "passwordTypePolicyOverride": { - "message": "Password type", + "message": "Tipo da senha", "description": "Name of the password generator policy that overrides the user's password/passphrase selection." }, "userPreference": { @@ -5723,7 +5729,7 @@ } }, "sendCreatedDescriptionPassword": { - "message": "Copy and share this Send link. The Send will be available to anyone with the link and password you set for the next $TIME$.", + "message": "Copie e compartilhe este link do Send. O Send ficará disponível para qualquer um com o link e a senha por $TIME$.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated.", "placeholders": { "time": { @@ -5733,7 +5739,7 @@ } }, "sendCreatedDescriptionEmail": { - "message": "Copy and share this Send link. It can be viewed by the people you specified for the next $TIME$.", + "message": "Copie e compartilhe este link do Send. Ele pode ser visto pelas pessoas que você especificou pelos próximos $TIME$.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated.", "placeholders": { "time": { @@ -5849,10 +5855,6 @@ "message": "Não sabe a senha? Peça ao remetente a senha necessária para acessar esse Send.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, - "sendHiddenByDefault": { - "message": "Este Send é oculto por padrão. Você pode alternar a visibilidade usando o botão abaixo.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, "downloadAttachments": { "message": "Baixar anexos" }, @@ -6145,6 +6147,21 @@ "autoConfirmCheckBoxLabel": { "message": "Eu aceito estes riscos e atualizações de política" }, + "autoConfirmEnabledByAdmin": { + "message": "Ativou a configuração de confirmação de usuários automática" + }, + "autoConfirmDisabledByAdmin": { + "message": "Desativou a configuração de confirmação de usuários automática" + }, + "autoConfirmEnabledByPortal": { + "message": "Adicionou a política de confirmação de usuários automática" + }, + "autoConfirmDisabledByPortal": { + "message": "Removeu a política de confirmação de usuários automática" + }, + "system": { + "message": "Sistema" + }, "personalOwnership": { "message": "Remover cofre individual" }, @@ -6439,7 +6456,7 @@ "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "verifyYourEmailToViewThisSend": { - "message": "Verify your email to view this Send", + "message": "Confirme seu e-mail para ver este Send", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "viewSendHiddenEmailWarning": { @@ -6687,10 +6704,10 @@ } }, "reinviteSuccessToast": { - "message": "1 invitation sent" + "message": "1 convite enviado" }, "bulkReinviteSentToast": { - "message": "$COUNT$ invitations sent", + "message": "$COUNT$ convites enviados", "placeholders": { "count": { "content": "$1", @@ -6716,7 +6733,7 @@ } }, "bulkReinviteProgressTitle": { - "message": "$COUNT$ of $TOTAL$ invitations sent...", + "message": "$COUNT$ dos $TOTAL$ convites foram enviados...", "placeholders": { "count": { "content": "$1", @@ -6729,10 +6746,10 @@ } }, "bulkReinviteProgressSubtitle": { - "message": "Keep this page open until all are sent." + "message": "Mantenha esta página aberta até que todos sejam enviados." }, "bulkReinviteFailuresTitle": { - "message": "$COUNT$ invitations didn't send", + "message": "$COUNT$ convites não foram enviados", "placeholders": { "count": { "content": "$1", @@ -6741,10 +6758,10 @@ } }, "bulkReinviteFailureTitle": { - "message": "1 invitation didn't send" + "message": "1 convite não foi enviado" }, "bulkReinviteFailureDescription": { - "message": "An error occurred while sending invitations to $COUNT$ of $TOTAL$ members. Try sending again, and if the problem continues,", + "message": "Ocorreu um erro ao enviar $COUNT$ convites para os $TOTAL$ membros. Tente enviar de novo, e se o problema continuar,", "placeholders": { "count": { "content": "$1", @@ -6757,7 +6774,7 @@ } }, "bulkResendInvitations": { - "message": "Try sending again" + "message": "Tentar enviar de novo" }, "bulkRemovedMessage": { "message": "Removido com sucesso" @@ -7092,16 +7109,16 @@ "message": "Uma ou mais políticas da organização impedem que você exporte seu cofre individual." }, "activateAutofillPolicy": { - "message": "Activate autofill" + "message": "Ativar preenchimento automático" }, "activateAutofillPolicyDescription": { - "message": "Activate the autofill on page load setting on the browser extension for all existing and new members." + "message": "Ative a configuração de preenchimento automático no carregamento da página na extensão do navegador para todos os membros existentes e novos." }, "autofillOnPageLoadExploitWarning": { - "message": "Compromised or untrusted websites can exploit autofill on page load." + "message": "Sites comprometidos ou não confiáveis podem explorar do preenchimento automático ao carregar a página." }, "learnMoreAboutAutofillPolicy": { - "message": "Learn more about autofill" + "message": "Saiba mais sobre preenchimento automático" }, "selectType": { "message": "Selecionar tipo de SSO" @@ -7400,6 +7417,9 @@ "invalidVerificationCode": { "message": "Código de verificação inválido" }, + "invalidEmailOrVerificationCode": { + "message": "Email ou código de verificação inválido" + }, "keyConnectorDomain": { "message": "Domínio do Key Connector" }, @@ -10198,7 +10218,7 @@ "message": "Atribuir tarefas" }, "allTasksAssigned": { - "message": "All tasks have been assigned" + "message": "Todas as tarefas foram atribuídas" }, "assignSecurityTasksToMembers": { "message": "Envie notificações para alteração de senhas" @@ -10608,7 +10628,7 @@ "message": "Falha ao salvar a integração. Tente novamente mais tarde." }, "mustBeOrganizationOwnerAdmin": { - "message": "You must be an Organization Owner or Admin to perform this action." + "message": "Você precisa ser proprietário ou administrador da organização para executar esta ação." }, "mustBeOrgOwnerToPerformAction": { "message": "Você precisa ser o proprietário da organização para executar esta ação." @@ -11539,13 +11559,13 @@ "message": "O Bitwarden tentará reivindicar o domínio 3 vezes durante as primeiras 72 horas. Se o domínio não poder ser reivindicado, confira o registro de DNS no seu servidor e reivindique manualmente. Se não for reivindicado, o domínio será removido da sua organização em 7 dias." }, "automaticDomainClaimProcess1": { - "message": "Bitwarden will attempt to claim the domain within 72 hours. If the domain can't be claimed, verify your DNS record and claim manually. Unclaimed domains are removed after 7 days." + "message": "Bitwarden tentará reivindicar o domínio dentro de 72 horas. Se o domínio não puder ser reivindicado, verifique o seu registro DNS e reivindique manualmente. Domínios não reivindicados são removidos após 7 dias." }, "automaticDomainClaimProcess2": { - "message": "Once claimed, existing members with claimed domains will be emailed about the " + "message": "Ao reivindicar, os membros existentes com domínios reivindicados serão enviados um e-mail sobre a " }, "accountOwnershipChange": { - "message": "account ownership change" + "message": "alteração de propriedade da conta" }, "automaticDomainClaimProcessEnd": { "message": "." @@ -11563,7 +11583,7 @@ "message": "Reivindicado" }, "domainStatusPending": { - "message": "Pending" + "message": "Pendente" }, "claimedDomainsDescription": { "message": "Reivindique um domínio para ser o proprietário das contas dos membros. A página do identificador do SSO será pulada durante a autenticação dos membros com os domínios reivindicados, e os administradores poderão apagar contas reivindicadas." @@ -11910,10 +11930,10 @@ "message": "Os itens arquivados aparecerão aqui e serão excluídos dos resultados gerais de busca e das sugestões de preenchimento automático." }, "itemArchiveToast": { - "message": "Item archived" + "message": "Item arquivado" }, "itemUnarchivedToast": { - "message": "Item unarchived" + "message": "Item desarquivado" }, "bulkArchiveItems": { "message": "Itens arquivados" @@ -12593,7 +12613,7 @@ "message": "Tem certeza que deseja continuar?" }, "errorCannotDecrypt": { - "message": "Error: Cannot decrypt" + "message": "Erro: Não é possível descriptografar" }, "userVerificationFailed": { "message": "Falha na verificação do usuário." @@ -12857,17 +12877,35 @@ "storageFullDescription": { "message": "Você usou todos os $GB$ GB do seu armazenamento criptografado. Para continuar armazenando arquivos, adicione mais armazenamento." }, + "extensionPromptHeading": { + "message": "Get the extension for easy vault access" + }, + "extensionPromptBody": { + "message": "With the browser extension installed, you'll take Bitwarden everywhere online. It'll fill in passwords, so you can log into your accounts with a single click." + }, + "extensionPromptImageAlt": { + "message": "A web browser showing the Bitwarden extension with autofill items for the current webpage." + }, + "skip": { + "message": "Skip" + }, + "downloadExtension": { + "message": "Download extension" + }, "whoCanView": { - "message": "Who can view" + "message": "Quem pode visualizar" }, "specificPeople": { - "message": "Specific people" + "message": "Pessoas específicas" }, "emailVerificationDesc": { - "message": "After sharing this Send link, individuals will need to verify their email with a code to view this Send." + "message": "Após compartilhar este link de Send, indivíduos precisarão verificar seus e-mails com um código para visualizar este Send." }, "enterMultipleEmailsSeparatedByComma": { - "message": "Enter multiple emails by separating with a comma." + "message": "Digite vários e-mails, separados com uma vírgula." + }, + "emailsRequiredChangeAccessType": { + "message": "A verificação de e-mail requer pelo menos um endereço de e-mail. Para remover todos, altere o tipo de acesso acima." }, "emailPlaceholder": { "message": "user@bitwarden.com , user@acme.com" @@ -12876,7 +12914,7 @@ "message": "Quando você remover o armazenamento, você receberá um crédito de conta proporcional que irá automaticamente para sua próxima fatura." }, "ownerBadgeA11yDescription": { - "message": "Owner, $OWNER$, show all items owned by $OWNER$", + "message": "Proprietário, $OWNER$, mostrar todos os itens pertencentes a $OWNER$", "placeholders": { "owner": { "content": "$1", @@ -12888,35 +12926,47 @@ "message": "Você tem o Premium" }, "emailProtected": { - "message": "E-mail protegido" + "message": "Protegido por e-mail" }, "invalidSendPassword": { - "message": "Invalid Send password" + "message": "Senha do Send inválida" + }, + "vaultWelcomeDialogTitle": { + "message": "Você entrou! Boas-vindas ao Bitwarden" + }, + "vaultWelcomeDialogDescription": { + "message": "Armazene todas as suas senhas e informações pessoais no seu cofre do Bitwarden. Vamos te dar um guia." + }, + "vaultWelcomeDialogPrimaryCta": { + "message": "Começar guia" + }, + "vaultWelcomeDialogDismissCta": { + "message": "Pular" }, "sendPasswordHelperText": { - "message": "Individuals will need to enter the password to view this Send", + "message": "Os indivíduos precisarão digitar a senha para ver este Send", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "perUser": { - "message": "per user" + "message": "por usuário" }, "upgradeToTeams": { - "message": "Upgrade to Teams" + "message": "Fazer upgrade para o Equipes" }, "upgradeToEnterprise": { - "message": "Upgrade to Enterprise" + "message": "Fazer upgrade para o Empresarial" }, "upgradeShareEvenMore": { - "message": "Share even more with Families, or get powerful, trusted password security with Teams or Enterprise" + "message": "Compartilhe ainda mais com o Famílias, ou receba segurança poderosa e confiável de senhas com o Equipes ou o Empresarial" }, "organizationUpgradeTaxInformationMessage": { - "message": "Prices exclude tax and are billed annually." + "message": "Os preços excluem os impostos e são cobrados anualmente." }, "invoicePreviewErrorMessage": { - "message": "Encountered an error while generating the invoice preview." + "message": "Foi deparado um erro ao gerar a pré-visualização da fatura." }, "planProratedMembershipInMonths": { - "message": "Prorated $PLAN$ membership ($NUMOFMONTHS$)", + "message": "Assinatura $PLAN$ rateada ($NUMOFMONTHS$)", "placeholders": { "plan": { "content": "$1", @@ -12929,16 +12979,16 @@ } }, "premiumSubscriptionCredit": { - "message": "Premium subscription credit" + "message": "Crédito da assinatura Premium" }, "enterpriseMembership": { - "message": "Enterprise membership" + "message": "Assinatura Empresarial" }, "teamsMembership": { - "message": "Teams membership" + "message": "Assinatura do Equipes" }, "plansUpdated": { - "message": "You've upgraded to $PLAN$!", + "message": "Você fez upgrade para o $PLAN$!", "placeholders": { "plan": { "content": "$1", @@ -12947,6 +12997,24 @@ } }, "paymentMethodUpdateError": { - "message": "There was an error updating your payment method." + "message": "Houve um erro ao atualizar seu método de pagamento." + }, + "sendPasswordInvalidAskOwner": { + "message": "Senha inválida. Peça ao remetente a senha necessária para acessar este Send.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "sendExpiresOn": { + "message": "Este send expirá às $TIME$ em $DATE$", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated.", + "placeholders": { + "time": { + "content": "$1", + "example": "10:00 AM" + }, + "date": { + "content": "$2", + "example": "Jan 1, 1970" + } + } } } diff --git a/apps/web/src/locales/pt_PT/messages.json b/apps/web/src/locales/pt_PT/messages.json index a20d884c321..55b35c60155 100644 --- a/apps/web/src/locales/pt_PT/messages.json +++ b/apps/web/src/locales/pt_PT/messages.json @@ -4337,6 +4337,15 @@ } } }, + "automaticallyConfirmedUserId": { + "message": "Utilizador $ID$ confirmado automaticamente.", + "placeholders": { + "id": { + "content": "$1", + "example": "John Smith" + } + } + }, "editedUserId": { "message": "Utilizador $ID$ editado.", "placeholders": { @@ -4596,29 +4605,26 @@ "generatingYourAccessIntelligence": { "message": "A gerar a sua Inteligência de Acesso..." }, - "fetchingMemberData": { - "message": "A obter dados dos membros..." - }, - "analyzingPasswordHealth": { - "message": "A analisar a segurança da palavra-passe..." - }, - "calculatingRiskScores": { - "message": "A calcular pontuações de risco..." - }, - "generatingReportData": { - "message": "A gerar dados do relatório..." - }, - "savingReport": { - "message": "A guardar relatório..." - }, - "compilingInsights": { - "message": "A compilar insights..." - }, "loadingProgress": { "message": "A carregar progresso" }, - "thisMightTakeFewMinutes": { - "message": "Isto pode demorar alguns minutos." + "reviewingMemberData": { + "message": "A rever os dados dos membros..." + }, + "analyzingPasswords": { + "message": "A analisar palavras-passe..." + }, + "calculatingRisks": { + "message": "A calcular riscos..." + }, + "generatingReports": { + "message": "A gerar relatórios..." + }, + "compilingInsightsProgress": { + "message": "A compilar insights..." + }, + "reportGenerationDone": { + "message": "Concluído!" }, "riskInsightsRunReport": { "message": "Executar relatório" @@ -5849,10 +5855,6 @@ "message": "Não sabe a palavra-passe? Peça ao remetente a palavra-passe necessária para aceder a este Send.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, - "sendHiddenByDefault": { - "message": "Este Send está oculto por defeito. Pode alternar a sua visibilidade utilizando o botão abaixo.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, "downloadAttachments": { "message": "Transferir anexos" }, @@ -6145,6 +6147,21 @@ "autoConfirmCheckBoxLabel": { "message": "Aceito estes riscos e atualizações da política" }, + "autoConfirmEnabledByAdmin": { + "message": "Definição de confirmação automática de utilizadores ativada" + }, + "autoConfirmDisabledByAdmin": { + "message": "Definição de confirmação automática de utilizadores desativada" + }, + "autoConfirmEnabledByPortal": { + "message": "Política de confirmação automática de utilizadores adicionada" + }, + "autoConfirmDisabledByPortal": { + "message": "Política de confirmação automática de utilizadores removida" + }, + "system": { + "message": "Sistema" + }, "personalOwnership": { "message": "Remover cofre pessoal" }, @@ -7400,6 +7417,9 @@ "invalidVerificationCode": { "message": "Código de verificação inválido" }, + "invalidEmailOrVerificationCode": { + "message": "E-mail ou código de verificação inválido" + }, "keyConnectorDomain": { "message": "Domínio do Key Connector" }, @@ -12857,6 +12877,21 @@ "storageFullDescription": { "message": "Utilizou os $GB$ GB do seu armazenamento encriptado. Para continuar a guardar ficheiros, adicione mais espaço de armazenamento." }, + "extensionPromptHeading": { + "message": "Obtenha a extensão para aceder facilmente ao seu cofre" + }, + "extensionPromptBody": { + "message": "Com a extensão do navegador instalada, terá o Bitwarden sempre disponível online. Esta preencherá automaticamente as palavras-passe, para que possa iniciar sessão nas suas contas com um único clique." + }, + "extensionPromptImageAlt": { + "message": "Um navegador web a apresentar a extensão Bitwarden com itens de preenchimento automático para a página atual." + }, + "skip": { + "message": "Saltar" + }, + "downloadExtension": { + "message": "Transferir extensão" + }, "whoCanView": { "message": "Quem pode ver" }, @@ -12869,6 +12904,9 @@ "enterMultipleEmailsSeparatedByComma": { "message": "Introduza vários e-mails, separados por vírgula." }, + "emailsRequiredChangeAccessType": { + "message": "A verificação por e-mail requer pelo menos um endereço de e-mail. Para remover todos os e-mails, altere o tipo de acesso acima." + }, "emailPlaceholder": { "message": "utilizador@bitwarden.com , utilizador@acme.com" }, @@ -12893,6 +12931,18 @@ "invalidSendPassword": { "message": "Palavra-passe do Send inválida" }, + "vaultWelcomeDialogTitle": { + "message": "Entrou com sucesso! Bem-vindo ao Bitwarden" + }, + "vaultWelcomeDialogDescription": { + "message": "Armazene todas as suas palavras-passe e informações pessoais no seu cofre Bitwarden. Vamos mostrar-lhe como funciona." + }, + "vaultWelcomeDialogPrimaryCta": { + "message": "Iniciar tour" + }, + "vaultWelcomeDialogDismissCta": { + "message": "Saltar" + }, "sendPasswordHelperText": { "message": "Os indivíduos terão de introduzir a palavra-passe para ver este Send", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." @@ -12948,5 +12998,23 @@ }, "paymentMethodUpdateError": { "message": "Ocorreu um erro ao atualizar o seu método de pagamento." + }, + "sendPasswordInvalidAskOwner": { + "message": "Palavra-passe inválida. Peça ao remetente a palavra-passe necessária para aceder a este Send.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "sendExpiresOn": { + "message": "Este Send expira às $TIME$ de $DATE$", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated.", + "placeholders": { + "time": { + "content": "$1", + "example": "10:00 AM" + }, + "date": { + "content": "$2", + "example": "Jan 1, 1970" + } + } } } diff --git a/apps/web/src/locales/ro/messages.json b/apps/web/src/locales/ro/messages.json index 6dca68fa932..8fe372fbc52 100644 --- a/apps/web/src/locales/ro/messages.json +++ b/apps/web/src/locales/ro/messages.json @@ -4337,6 +4337,15 @@ } } }, + "automaticallyConfirmedUserId": { + "message": "Automatically confirmed user $ID$.", + "placeholders": { + "id": { + "content": "$1", + "example": "John Smith" + } + } + }, "editedUserId": { "message": "Utilizatorul $ID$ a fost editat.", "placeholders": { @@ -4596,29 +4605,26 @@ "generatingYourAccessIntelligence": { "message": "Generating your Access Intelligence..." }, - "fetchingMemberData": { - "message": "Fetching member data..." - }, - "analyzingPasswordHealth": { - "message": "Analyzing password health..." - }, - "calculatingRiskScores": { - "message": "Calculating risk scores..." - }, - "generatingReportData": { - "message": "Generating report data..." - }, - "savingReport": { - "message": "Saving report..." - }, - "compilingInsights": { - "message": "Compiling insights..." - }, "loadingProgress": { "message": "Loading progress" }, - "thisMightTakeFewMinutes": { - "message": "This might take a few minutes." + "reviewingMemberData": { + "message": "Reviewing member data..." + }, + "analyzingPasswords": { + "message": "Analyzing passwords..." + }, + "calculatingRisks": { + "message": "Calculating risks..." + }, + "generatingReports": { + "message": "Generating reports..." + }, + "compilingInsightsProgress": { + "message": "Compiling insights..." + }, + "reportGenerationDone": { + "message": "Done!" }, "riskInsightsRunReport": { "message": "Run report" @@ -5849,10 +5855,6 @@ "message": "Nu știți parola? Solicitați expeditorului parola necesară pentru a accesa acest Send.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, - "sendHiddenByDefault": { - "message": "Acest Send este ascuns în mod implicit. Puteți comuta vizibilitatea acestuia cu butonul de mai jos.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, "downloadAttachments": { "message": "Download attachments" }, @@ -6145,6 +6147,21 @@ "autoConfirmCheckBoxLabel": { "message": "I accept these risks and policy updates" }, + "autoConfirmEnabledByAdmin": { + "message": "Turned on Automatic user confirmation setting" + }, + "autoConfirmDisabledByAdmin": { + "message": "Turned off Automatic user confirmation setting" + }, + "autoConfirmEnabledByPortal": { + "message": "Added Automatic user confirmation policy" + }, + "autoConfirmDisabledByPortal": { + "message": "Removed Automatic user confirmation policy" + }, + "system": { + "message": "System" + }, "personalOwnership": { "message": "Înlăturați seiful personal" }, @@ -7400,6 +7417,9 @@ "invalidVerificationCode": { "message": "Cod de verificare nevalid" }, + "invalidEmailOrVerificationCode": { + "message": "Invalid email or verification code" + }, "keyConnectorDomain": { "message": "Key Connector domain" }, @@ -12857,6 +12877,21 @@ "storageFullDescription": { "message": "You have used all $GB$ GB of your encrypted storage. To continue storing files, add more storage." }, + "extensionPromptHeading": { + "message": "Get the extension for easy vault access" + }, + "extensionPromptBody": { + "message": "With the browser extension installed, you'll take Bitwarden everywhere online. It'll fill in passwords, so you can log into your accounts with a single click." + }, + "extensionPromptImageAlt": { + "message": "A web browser showing the Bitwarden extension with autofill items for the current webpage." + }, + "skip": { + "message": "Skip" + }, + "downloadExtension": { + "message": "Download extension" + }, "whoCanView": { "message": "Who can view" }, @@ -12869,6 +12904,9 @@ "enterMultipleEmailsSeparatedByComma": { "message": "Enter multiple emails by separating with a comma." }, + "emailsRequiredChangeAccessType": { + "message": "Email verification requires at least one email address. To remove all emails, change the access type above." + }, "emailPlaceholder": { "message": "user@bitwarden.com , user@acme.com" }, @@ -12893,6 +12931,18 @@ "invalidSendPassword": { "message": "Invalid Send password" }, + "vaultWelcomeDialogTitle": { + "message": "You're in! Welcome to Bitwarden" + }, + "vaultWelcomeDialogDescription": { + "message": "Store all your passwords and personal info in your Bitwarden vault. We'll show you around." + }, + "vaultWelcomeDialogPrimaryCta": { + "message": "Start tour" + }, + "vaultWelcomeDialogDismissCta": { + "message": "Skip" + }, "sendPasswordHelperText": { "message": "Individuals will need to enter the password to view this Send", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." @@ -12948,5 +12998,23 @@ }, "paymentMethodUpdateError": { "message": "There was an error updating your payment method." + }, + "sendPasswordInvalidAskOwner": { + "message": "Invalid password. Ask the sender for the password needed to access this Send.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "sendExpiresOn": { + "message": "This Send expires at $TIME$ on $DATE$", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated.", + "placeholders": { + "time": { + "content": "$1", + "example": "10:00 AM" + }, + "date": { + "content": "$2", + "example": "Jan 1, 1970" + } + } } } diff --git a/apps/web/src/locales/ru/messages.json b/apps/web/src/locales/ru/messages.json index 13aec65ba3e..6687b59fc86 100644 --- a/apps/web/src/locales/ru/messages.json +++ b/apps/web/src/locales/ru/messages.json @@ -4337,6 +4337,15 @@ } } }, + "automaticallyConfirmedUserId": { + "message": "Автоматически подтвержденный пользователь $ID$.", + "placeholders": { + "id": { + "content": "$1", + "example": "John Smith" + } + } + }, "editedUserId": { "message": "Изменен пользователь $ID$.", "placeholders": { @@ -4596,29 +4605,26 @@ "generatingYourAccessIntelligence": { "message": "Ваша информация о доступе генерируется..." }, - "fetchingMemberData": { - "message": "Получение данных о пользователях..." - }, - "analyzingPasswordHealth": { - "message": "Анализ здоровья пароля..." - }, - "calculatingRiskScores": { - "message": "Расчет показателей риска..." - }, - "generatingReportData": { - "message": "Генерация данных отчета..." - }, - "savingReport": { - "message": "Сохранение отчета..." - }, - "compilingInsights": { - "message": "Компиляция информации..." - }, "loadingProgress": { "message": "Прогресс загрузки" }, - "thisMightTakeFewMinutes": { - "message": "Это может занять несколько минут." + "reviewingMemberData": { + "message": "Проверка данных пользователя..." + }, + "analyzingPasswords": { + "message": "Анализ паролей..." + }, + "calculatingRisks": { + "message": "Расчет рисков..." + }, + "generatingReports": { + "message": "Формирование отчетов..." + }, + "compilingInsightsProgress": { + "message": "Компиляция информации..." + }, + "reportGenerationDone": { + "message": "Готово!" }, "riskInsightsRunReport": { "message": "Запустить отчет" @@ -5849,10 +5855,6 @@ "message": "Не знаете пароль? Для доступа к этой Send, запросите его у отправителя.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, - "sendHiddenByDefault": { - "message": "Эта Send по умолчанию скрыта. Вы можете переключить ее видимость с помощью кнопки ниже.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, "downloadAttachments": { "message": "Скачать вложения" }, @@ -6145,6 +6147,21 @@ "autoConfirmCheckBoxLabel": { "message": "Я принимаю эти риски и политики обновления" }, + "autoConfirmEnabledByAdmin": { + "message": "Включена настройка автоматического подтверждения пользователей" + }, + "autoConfirmDisabledByAdmin": { + "message": "Отключена настройка автоматического подтверждения пользователей" + }, + "autoConfirmEnabledByPortal": { + "message": "Добавлена политика автоматического подтверждения пользователей" + }, + "autoConfirmDisabledByPortal": { + "message": "Удалена политика автоматического подтверждения пользователей" + }, + "system": { + "message": "Система" + }, "personalOwnership": { "message": "Удалить личное хранилище" }, @@ -7400,6 +7417,9 @@ "invalidVerificationCode": { "message": "Неверный код подтверждения" }, + "invalidEmailOrVerificationCode": { + "message": "Неверный email или код подтверждения" + }, "keyConnectorDomain": { "message": "Домен соединителя ключей" }, @@ -12857,6 +12877,21 @@ "storageFullDescription": { "message": "Вы использовали все $GB$ вашего зашифрованного хранилища. Чтобы продолжить хранение файлов, добавьте дополнительное хранилище." }, + "extensionPromptHeading": { + "message": "Установите расширение для удобного доступа к хранилищу" + }, + "extensionPromptBody": { + "message": "Установив расширение для браузера, вы сможете использовать Bitwarden везде, где есть интернет. Оно будет вводить пароли, так что вы сможете входить в свои аккаунты одним щелчком мыши." + }, + "extensionPromptImageAlt": { + "message": "Браузер, отображающий расширение Bitwarden с элементами автозаполнения для текущей веб-страницы." + }, + "skip": { + "message": "Пропустить" + }, + "downloadExtension": { + "message": "Скачать расширение" + }, "whoCanView": { "message": "Кто может просматривать" }, @@ -12869,6 +12904,9 @@ "enterMultipleEmailsSeparatedByComma": { "message": "Введите несколько email, разделяя их запятой." }, + "emailsRequiredChangeAccessType": { + "message": "Для проверки электронной почты требуется как минимум один адрес email. Чтобы удалить все адреса электронной почты, измените тип доступа выше." + }, "emailPlaceholder": { "message": "user@bitwarden.com , user@acme.com" }, @@ -12893,6 +12931,18 @@ "invalidSendPassword": { "message": "Неверный пароль Send" }, + "vaultWelcomeDialogTitle": { + "message": "Вы с нами! Добро пожаловать в Bitwarden" + }, + "vaultWelcomeDialogDescription": { + "message": "Сохраняйте все свои пароли и личную информацию в хранилище Bitwarden. Мы покажем как это работает." + }, + "vaultWelcomeDialogPrimaryCta": { + "message": "Начать знакомство" + }, + "vaultWelcomeDialogDismissCta": { + "message": "Пропустить" + }, "sendPasswordHelperText": { "message": "Пользователям необходимо будет ввести пароль для просмотра этой Send", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." @@ -12948,5 +12998,23 @@ }, "paymentMethodUpdateError": { "message": "Произошла ошибка при обновлении способа оплаты." + }, + "sendPasswordInvalidAskOwner": { + "message": "Неверный пароль. Для доступа к этой Send, запросите его у отправителя.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "sendExpiresOn": { + "message": "This Send expires at $TIME$ on $DATE$", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated.", + "placeholders": { + "time": { + "content": "$1", + "example": "10:00 AM" + }, + "date": { + "content": "$2", + "example": "Jan 1, 1970" + } + } } } diff --git a/apps/web/src/locales/si/messages.json b/apps/web/src/locales/si/messages.json index ab7c7e30566..3bc5fa153d9 100644 --- a/apps/web/src/locales/si/messages.json +++ b/apps/web/src/locales/si/messages.json @@ -4337,6 +4337,15 @@ } } }, + "automaticallyConfirmedUserId": { + "message": "Automatically confirmed user $ID$.", + "placeholders": { + "id": { + "content": "$1", + "example": "John Smith" + } + } + }, "editedUserId": { "message": "Edited user $ID$.", "placeholders": { @@ -4596,29 +4605,26 @@ "generatingYourAccessIntelligence": { "message": "Generating your Access Intelligence..." }, - "fetchingMemberData": { - "message": "Fetching member data..." - }, - "analyzingPasswordHealth": { - "message": "Analyzing password health..." - }, - "calculatingRiskScores": { - "message": "Calculating risk scores..." - }, - "generatingReportData": { - "message": "Generating report data..." - }, - "savingReport": { - "message": "Saving report..." - }, - "compilingInsights": { - "message": "Compiling insights..." - }, "loadingProgress": { "message": "Loading progress" }, - "thisMightTakeFewMinutes": { - "message": "This might take a few minutes." + "reviewingMemberData": { + "message": "Reviewing member data..." + }, + "analyzingPasswords": { + "message": "Analyzing passwords..." + }, + "calculatingRisks": { + "message": "Calculating risks..." + }, + "generatingReports": { + "message": "Generating reports..." + }, + "compilingInsightsProgress": { + "message": "Compiling insights..." + }, + "reportGenerationDone": { + "message": "Done!" }, "riskInsightsRunReport": { "message": "Run report" @@ -5849,10 +5855,6 @@ "message": "Don't know the password? Ask the sender for the password needed to access this Send.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, - "sendHiddenByDefault": { - "message": "This Send is hidden by default. You can toggle its visibility using the button below.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, "downloadAttachments": { "message": "Download attachments" }, @@ -6145,6 +6147,21 @@ "autoConfirmCheckBoxLabel": { "message": "I accept these risks and policy updates" }, + "autoConfirmEnabledByAdmin": { + "message": "Turned on Automatic user confirmation setting" + }, + "autoConfirmDisabledByAdmin": { + "message": "Turned off Automatic user confirmation setting" + }, + "autoConfirmEnabledByPortal": { + "message": "Added Automatic user confirmation policy" + }, + "autoConfirmDisabledByPortal": { + "message": "Removed Automatic user confirmation policy" + }, + "system": { + "message": "System" + }, "personalOwnership": { "message": "Remove individual vault" }, @@ -7400,6 +7417,9 @@ "invalidVerificationCode": { "message": "Invalid verification code" }, + "invalidEmailOrVerificationCode": { + "message": "Invalid email or verification code" + }, "keyConnectorDomain": { "message": "Key Connector domain" }, @@ -12857,6 +12877,21 @@ "storageFullDescription": { "message": "You have used all $GB$ GB of your encrypted storage. To continue storing files, add more storage." }, + "extensionPromptHeading": { + "message": "Get the extension for easy vault access" + }, + "extensionPromptBody": { + "message": "With the browser extension installed, you'll take Bitwarden everywhere online. It'll fill in passwords, so you can log into your accounts with a single click." + }, + "extensionPromptImageAlt": { + "message": "A web browser showing the Bitwarden extension with autofill items for the current webpage." + }, + "skip": { + "message": "Skip" + }, + "downloadExtension": { + "message": "Download extension" + }, "whoCanView": { "message": "Who can view" }, @@ -12869,6 +12904,9 @@ "enterMultipleEmailsSeparatedByComma": { "message": "Enter multiple emails by separating with a comma." }, + "emailsRequiredChangeAccessType": { + "message": "Email verification requires at least one email address. To remove all emails, change the access type above." + }, "emailPlaceholder": { "message": "user@bitwarden.com , user@acme.com" }, @@ -12893,6 +12931,18 @@ "invalidSendPassword": { "message": "Invalid Send password" }, + "vaultWelcomeDialogTitle": { + "message": "You're in! Welcome to Bitwarden" + }, + "vaultWelcomeDialogDescription": { + "message": "Store all your passwords and personal info in your Bitwarden vault. We'll show you around." + }, + "vaultWelcomeDialogPrimaryCta": { + "message": "Start tour" + }, + "vaultWelcomeDialogDismissCta": { + "message": "Skip" + }, "sendPasswordHelperText": { "message": "Individuals will need to enter the password to view this Send", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." @@ -12948,5 +12998,23 @@ }, "paymentMethodUpdateError": { "message": "There was an error updating your payment method." + }, + "sendPasswordInvalidAskOwner": { + "message": "Invalid password. Ask the sender for the password needed to access this Send.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "sendExpiresOn": { + "message": "This Send expires at $TIME$ on $DATE$", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated.", + "placeholders": { + "time": { + "content": "$1", + "example": "10:00 AM" + }, + "date": { + "content": "$2", + "example": "Jan 1, 1970" + } + } } } diff --git a/apps/web/src/locales/sk/messages.json b/apps/web/src/locales/sk/messages.json index b1f38615a99..6a25d6d0f3f 100644 --- a/apps/web/src/locales/sk/messages.json +++ b/apps/web/src/locales/sk/messages.json @@ -4337,6 +4337,15 @@ } } }, + "automaticallyConfirmedUserId": { + "message": "Automatically confirmed user $ID$.", + "placeholders": { + "id": { + "content": "$1", + "example": "John Smith" + } + } + }, "editedUserId": { "message": "Používateľ $ID$ upravený.", "placeholders": { @@ -4596,29 +4605,26 @@ "generatingYourAccessIntelligence": { "message": "Generuje sa prehľad o prístupe..." }, - "fetchingMemberData": { - "message": "Sťahujú sa dáta o členoch..." - }, - "analyzingPasswordHealth": { - "message": "Analyzuje sa odolnosť hesiel..." - }, - "calculatingRiskScores": { - "message": "Vypočítava sa úroveň ohrozenia..." - }, - "generatingReportData": { - "message": "Generujú sa dáta reportu..." - }, - "savingReport": { - "message": "Ukladá sa report..." - }, - "compilingInsights": { - "message": "Kompiluje sa prehľad..." - }, "loadingProgress": { "message": "Priebeh načítania" }, - "thisMightTakeFewMinutes": { - "message": "Môže to trvať niekoľko minút." + "reviewingMemberData": { + "message": "Reviewing member data..." + }, + "analyzingPasswords": { + "message": "Analyzing passwords..." + }, + "calculatingRisks": { + "message": "Calculating risks..." + }, + "generatingReports": { + "message": "Generating reports..." + }, + "compilingInsightsProgress": { + "message": "Compiling insights..." + }, + "reportGenerationDone": { + "message": "Done!" }, "riskInsightsRunReport": { "message": "Generovať report" @@ -5849,10 +5855,6 @@ "message": "Neviete heslo? Požiadajte odosielateľa o heslo potrebné k prístupu k tomuto Sendu.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, - "sendHiddenByDefault": { - "message": "Tento Send je normálne skrytý. Tlačidlom nižšie môžete prepnúť jeho viditeľnosť.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, "downloadAttachments": { "message": "Stiahnuť prílohy" }, @@ -6145,6 +6147,21 @@ "autoConfirmCheckBoxLabel": { "message": "Akceptujem tieto riziká a aktualizácie pravidiel" }, + "autoConfirmEnabledByAdmin": { + "message": "Turned on Automatic user confirmation setting" + }, + "autoConfirmDisabledByAdmin": { + "message": "Turned off Automatic user confirmation setting" + }, + "autoConfirmEnabledByPortal": { + "message": "Added Automatic user confirmation policy" + }, + "autoConfirmDisabledByPortal": { + "message": "Removed Automatic user confirmation policy" + }, + "system": { + "message": "System" + }, "personalOwnership": { "message": "Zakázať osobný trezor" }, @@ -7400,6 +7417,9 @@ "invalidVerificationCode": { "message": "Neplatný verifikačný kód" }, + "invalidEmailOrVerificationCode": { + "message": "Invalid email or verification code" + }, "keyConnectorDomain": { "message": "Doména Key Connectora" }, @@ -12857,6 +12877,21 @@ "storageFullDescription": { "message": "Použili ste všetkých $GB$ GB vášho šifrovaného úložiska. Ak chcete uložiť ďalšie súbory, pridajte viac úložiska." }, + "extensionPromptHeading": { + "message": "Get the extension for easy vault access" + }, + "extensionPromptBody": { + "message": "With the browser extension installed, you'll take Bitwarden everywhere online. It'll fill in passwords, so you can log into your accounts with a single click." + }, + "extensionPromptImageAlt": { + "message": "A web browser showing the Bitwarden extension with autofill items for the current webpage." + }, + "skip": { + "message": "Skip" + }, + "downloadExtension": { + "message": "Download extension" + }, "whoCanView": { "message": "Who can view" }, @@ -12869,6 +12904,9 @@ "enterMultipleEmailsSeparatedByComma": { "message": "Enter multiple emails by separating with a comma." }, + "emailsRequiredChangeAccessType": { + "message": "Email verification requires at least one email address. To remove all emails, change the access type above." + }, "emailPlaceholder": { "message": "user@bitwarden.com , user@acme.com" }, @@ -12893,6 +12931,18 @@ "invalidSendPassword": { "message": "Invalid Send password" }, + "vaultWelcomeDialogTitle": { + "message": "You're in! Welcome to Bitwarden" + }, + "vaultWelcomeDialogDescription": { + "message": "Store all your passwords and personal info in your Bitwarden vault. We'll show you around." + }, + "vaultWelcomeDialogPrimaryCta": { + "message": "Start tour" + }, + "vaultWelcomeDialogDismissCta": { + "message": "Skip" + }, "sendPasswordHelperText": { "message": "Individuals will need to enter the password to view this Send", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." @@ -12948,5 +12998,23 @@ }, "paymentMethodUpdateError": { "message": "There was an error updating your payment method." + }, + "sendPasswordInvalidAskOwner": { + "message": "Invalid password. Ask the sender for the password needed to access this Send.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "sendExpiresOn": { + "message": "This Send expires at $TIME$ on $DATE$", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated.", + "placeholders": { + "time": { + "content": "$1", + "example": "10:00 AM" + }, + "date": { + "content": "$2", + "example": "Jan 1, 1970" + } + } } } diff --git a/apps/web/src/locales/sl/messages.json b/apps/web/src/locales/sl/messages.json index db65260f9b1..4553f68c54b 100644 --- a/apps/web/src/locales/sl/messages.json +++ b/apps/web/src/locales/sl/messages.json @@ -4337,6 +4337,15 @@ } } }, + "automaticallyConfirmedUserId": { + "message": "Automatically confirmed user $ID$.", + "placeholders": { + "id": { + "content": "$1", + "example": "John Smith" + } + } + }, "editedUserId": { "message": "Edited user $ID$.", "placeholders": { @@ -4596,29 +4605,26 @@ "generatingYourAccessIntelligence": { "message": "Generating your Access Intelligence..." }, - "fetchingMemberData": { - "message": "Fetching member data..." - }, - "analyzingPasswordHealth": { - "message": "Analyzing password health..." - }, - "calculatingRiskScores": { - "message": "Calculating risk scores..." - }, - "generatingReportData": { - "message": "Generating report data..." - }, - "savingReport": { - "message": "Saving report..." - }, - "compilingInsights": { - "message": "Compiling insights..." - }, "loadingProgress": { "message": "Loading progress" }, - "thisMightTakeFewMinutes": { - "message": "This might take a few minutes." + "reviewingMemberData": { + "message": "Reviewing member data..." + }, + "analyzingPasswords": { + "message": "Analyzing passwords..." + }, + "calculatingRisks": { + "message": "Calculating risks..." + }, + "generatingReports": { + "message": "Generating reports..." + }, + "compilingInsightsProgress": { + "message": "Compiling insights..." + }, + "reportGenerationDone": { + "message": "Done!" }, "riskInsightsRunReport": { "message": "Run report" @@ -5849,10 +5855,6 @@ "message": "Don't know the password? Ask the sender for the password needed to access this Send.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, - "sendHiddenByDefault": { - "message": "This Send is hidden by default. You can toggle its visibility using the button below.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, "downloadAttachments": { "message": "Download attachments" }, @@ -6145,6 +6147,21 @@ "autoConfirmCheckBoxLabel": { "message": "I accept these risks and policy updates" }, + "autoConfirmEnabledByAdmin": { + "message": "Turned on Automatic user confirmation setting" + }, + "autoConfirmDisabledByAdmin": { + "message": "Turned off Automatic user confirmation setting" + }, + "autoConfirmEnabledByPortal": { + "message": "Added Automatic user confirmation policy" + }, + "autoConfirmDisabledByPortal": { + "message": "Removed Automatic user confirmation policy" + }, + "system": { + "message": "System" + }, "personalOwnership": { "message": "Remove individual vault" }, @@ -7400,6 +7417,9 @@ "invalidVerificationCode": { "message": "Invalid verification code" }, + "invalidEmailOrVerificationCode": { + "message": "Invalid email or verification code" + }, "keyConnectorDomain": { "message": "Key Connector domain" }, @@ -12857,6 +12877,21 @@ "storageFullDescription": { "message": "You have used all $GB$ GB of your encrypted storage. To continue storing files, add more storage." }, + "extensionPromptHeading": { + "message": "Get the extension for easy vault access" + }, + "extensionPromptBody": { + "message": "With the browser extension installed, you'll take Bitwarden everywhere online. It'll fill in passwords, so you can log into your accounts with a single click." + }, + "extensionPromptImageAlt": { + "message": "A web browser showing the Bitwarden extension with autofill items for the current webpage." + }, + "skip": { + "message": "Skip" + }, + "downloadExtension": { + "message": "Download extension" + }, "whoCanView": { "message": "Who can view" }, @@ -12869,6 +12904,9 @@ "enterMultipleEmailsSeparatedByComma": { "message": "Enter multiple emails by separating with a comma." }, + "emailsRequiredChangeAccessType": { + "message": "Email verification requires at least one email address. To remove all emails, change the access type above." + }, "emailPlaceholder": { "message": "user@bitwarden.com , user@acme.com" }, @@ -12893,6 +12931,18 @@ "invalidSendPassword": { "message": "Invalid Send password" }, + "vaultWelcomeDialogTitle": { + "message": "You're in! Welcome to Bitwarden" + }, + "vaultWelcomeDialogDescription": { + "message": "Store all your passwords and personal info in your Bitwarden vault. We'll show you around." + }, + "vaultWelcomeDialogPrimaryCta": { + "message": "Start tour" + }, + "vaultWelcomeDialogDismissCta": { + "message": "Skip" + }, "sendPasswordHelperText": { "message": "Individuals will need to enter the password to view this Send", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." @@ -12948,5 +12998,23 @@ }, "paymentMethodUpdateError": { "message": "There was an error updating your payment method." + }, + "sendPasswordInvalidAskOwner": { + "message": "Invalid password. Ask the sender for the password needed to access this Send.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "sendExpiresOn": { + "message": "This Send expires at $TIME$ on $DATE$", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated.", + "placeholders": { + "time": { + "content": "$1", + "example": "10:00 AM" + }, + "date": { + "content": "$2", + "example": "Jan 1, 1970" + } + } } } diff --git a/apps/web/src/locales/sr_CS/messages.json b/apps/web/src/locales/sr_CS/messages.json index fd18cb42a06..24ff0eb2f9f 100644 --- a/apps/web/src/locales/sr_CS/messages.json +++ b/apps/web/src/locales/sr_CS/messages.json @@ -4337,6 +4337,15 @@ } } }, + "automaticallyConfirmedUserId": { + "message": "Automatically confirmed user $ID$.", + "placeholders": { + "id": { + "content": "$1", + "example": "John Smith" + } + } + }, "editedUserId": { "message": "Edited user $ID$.", "placeholders": { @@ -4596,29 +4605,26 @@ "generatingYourAccessIntelligence": { "message": "Generating your Access Intelligence..." }, - "fetchingMemberData": { - "message": "Fetching member data..." - }, - "analyzingPasswordHealth": { - "message": "Analyzing password health..." - }, - "calculatingRiskScores": { - "message": "Calculating risk scores..." - }, - "generatingReportData": { - "message": "Generating report data..." - }, - "savingReport": { - "message": "Saving report..." - }, - "compilingInsights": { - "message": "Compiling insights..." - }, "loadingProgress": { "message": "Loading progress" }, - "thisMightTakeFewMinutes": { - "message": "This might take a few minutes." + "reviewingMemberData": { + "message": "Reviewing member data..." + }, + "analyzingPasswords": { + "message": "Analyzing passwords..." + }, + "calculatingRisks": { + "message": "Calculating risks..." + }, + "generatingReports": { + "message": "Generating reports..." + }, + "compilingInsightsProgress": { + "message": "Compiling insights..." + }, + "reportGenerationDone": { + "message": "Done!" }, "riskInsightsRunReport": { "message": "Run report" @@ -5849,10 +5855,6 @@ "message": "Don't know the password? Ask the sender for the password needed to access this Send.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, - "sendHiddenByDefault": { - "message": "This Send is hidden by default. You can toggle its visibility using the button below.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, "downloadAttachments": { "message": "Download attachments" }, @@ -6145,6 +6147,21 @@ "autoConfirmCheckBoxLabel": { "message": "I accept these risks and policy updates" }, + "autoConfirmEnabledByAdmin": { + "message": "Turned on Automatic user confirmation setting" + }, + "autoConfirmDisabledByAdmin": { + "message": "Turned off Automatic user confirmation setting" + }, + "autoConfirmEnabledByPortal": { + "message": "Added Automatic user confirmation policy" + }, + "autoConfirmDisabledByPortal": { + "message": "Removed Automatic user confirmation policy" + }, + "system": { + "message": "System" + }, "personalOwnership": { "message": "Remove individual vault" }, @@ -7400,6 +7417,9 @@ "invalidVerificationCode": { "message": "Invalid verification code" }, + "invalidEmailOrVerificationCode": { + "message": "Invalid email or verification code" + }, "keyConnectorDomain": { "message": "Key Connector domain" }, @@ -12857,6 +12877,21 @@ "storageFullDescription": { "message": "You have used all $GB$ GB of your encrypted storage. To continue storing files, add more storage." }, + "extensionPromptHeading": { + "message": "Get the extension for easy vault access" + }, + "extensionPromptBody": { + "message": "With the browser extension installed, you'll take Bitwarden everywhere online. It'll fill in passwords, so you can log into your accounts with a single click." + }, + "extensionPromptImageAlt": { + "message": "A web browser showing the Bitwarden extension with autofill items for the current webpage." + }, + "skip": { + "message": "Skip" + }, + "downloadExtension": { + "message": "Download extension" + }, "whoCanView": { "message": "Who can view" }, @@ -12869,6 +12904,9 @@ "enterMultipleEmailsSeparatedByComma": { "message": "Enter multiple emails by separating with a comma." }, + "emailsRequiredChangeAccessType": { + "message": "Email verification requires at least one email address. To remove all emails, change the access type above." + }, "emailPlaceholder": { "message": "user@bitwarden.com , user@acme.com" }, @@ -12893,6 +12931,18 @@ "invalidSendPassword": { "message": "Invalid Send password" }, + "vaultWelcomeDialogTitle": { + "message": "You're in! Welcome to Bitwarden" + }, + "vaultWelcomeDialogDescription": { + "message": "Store all your passwords and personal info in your Bitwarden vault. We'll show you around." + }, + "vaultWelcomeDialogPrimaryCta": { + "message": "Start tour" + }, + "vaultWelcomeDialogDismissCta": { + "message": "Skip" + }, "sendPasswordHelperText": { "message": "Individuals will need to enter the password to view this Send", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." @@ -12948,5 +12998,23 @@ }, "paymentMethodUpdateError": { "message": "There was an error updating your payment method." + }, + "sendPasswordInvalidAskOwner": { + "message": "Invalid password. Ask the sender for the password needed to access this Send.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "sendExpiresOn": { + "message": "This Send expires at $TIME$ on $DATE$", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated.", + "placeholders": { + "time": { + "content": "$1", + "example": "10:00 AM" + }, + "date": { + "content": "$2", + "example": "Jan 1, 1970" + } + } } } diff --git a/apps/web/src/locales/sr_CY/messages.json b/apps/web/src/locales/sr_CY/messages.json index eb6484db8df..f0878f973c2 100644 --- a/apps/web/src/locales/sr_CY/messages.json +++ b/apps/web/src/locales/sr_CY/messages.json @@ -4337,6 +4337,15 @@ } } }, + "automaticallyConfirmedUserId": { + "message": "Automatically confirmed user $ID$.", + "placeholders": { + "id": { + "content": "$1", + "example": "John Smith" + } + } + }, "editedUserId": { "message": "Корисник $ID$ промењен.", "placeholders": { @@ -4596,29 +4605,26 @@ "generatingYourAccessIntelligence": { "message": "Generating your Access Intelligence..." }, - "fetchingMemberData": { - "message": "Преузимање података о члановима..." - }, - "analyzingPasswordHealth": { - "message": "Анализа здравља лозинки..." - }, - "calculatingRiskScores": { - "message": "Израчунавање резултата ризика..." - }, - "generatingReportData": { - "message": "Генерисање података извештаја..." - }, - "savingReport": { - "message": "Чување извештаја..." - }, - "compilingInsights": { - "message": "Састављање увида..." - }, "loadingProgress": { "message": "Учитавање напретка" }, - "thisMightTakeFewMinutes": { - "message": "Ово може потрајати неколико минута." + "reviewingMemberData": { + "message": "Reviewing member data..." + }, + "analyzingPasswords": { + "message": "Analyzing passwords..." + }, + "calculatingRisks": { + "message": "Calculating risks..." + }, + "generatingReports": { + "message": "Generating reports..." + }, + "compilingInsightsProgress": { + "message": "Compiling insights..." + }, + "reportGenerationDone": { + "message": "Done!" }, "riskInsightsRunReport": { "message": "Покрените извештај" @@ -5849,10 +5855,6 @@ "message": "Не знате лозинку? Затражите од пошиљаоца лозинку потребну за приступ овом Слању.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, - "sendHiddenByDefault": { - "message": "Ово Слање је подразумевано скривено. Можете да пребацујете његову видљивост помоћу дугмета испод.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, "downloadAttachments": { "message": "Преузмите прилоге" }, @@ -6145,6 +6147,21 @@ "autoConfirmCheckBoxLabel": { "message": "Прихватам ове ризике и ажурирања политика" }, + "autoConfirmEnabledByAdmin": { + "message": "Turned on Automatic user confirmation setting" + }, + "autoConfirmDisabledByAdmin": { + "message": "Turned off Automatic user confirmation setting" + }, + "autoConfirmEnabledByPortal": { + "message": "Added Automatic user confirmation policy" + }, + "autoConfirmDisabledByPortal": { + "message": "Removed Automatic user confirmation policy" + }, + "system": { + "message": "System" + }, "personalOwnership": { "message": "Лично власништво" }, @@ -7400,6 +7417,9 @@ "invalidVerificationCode": { "message": "Неисправан верификациони код" }, + "invalidEmailOrVerificationCode": { + "message": "Invalid email or verification code" + }, "keyConnectorDomain": { "message": "Домен конектора кључа" }, @@ -12857,6 +12877,21 @@ "storageFullDescription": { "message": "You have used all $GB$ GB of your encrypted storage. To continue storing files, add more storage." }, + "extensionPromptHeading": { + "message": "Get the extension for easy vault access" + }, + "extensionPromptBody": { + "message": "With the browser extension installed, you'll take Bitwarden everywhere online. It'll fill in passwords, so you can log into your accounts with a single click." + }, + "extensionPromptImageAlt": { + "message": "A web browser showing the Bitwarden extension with autofill items for the current webpage." + }, + "skip": { + "message": "Skip" + }, + "downloadExtension": { + "message": "Download extension" + }, "whoCanView": { "message": "Who can view" }, @@ -12869,6 +12904,9 @@ "enterMultipleEmailsSeparatedByComma": { "message": "Enter multiple emails by separating with a comma." }, + "emailsRequiredChangeAccessType": { + "message": "Email verification requires at least one email address. To remove all emails, change the access type above." + }, "emailPlaceholder": { "message": "user@bitwarden.com , user@acme.com" }, @@ -12893,6 +12931,18 @@ "invalidSendPassword": { "message": "Invalid Send password" }, + "vaultWelcomeDialogTitle": { + "message": "You're in! Welcome to Bitwarden" + }, + "vaultWelcomeDialogDescription": { + "message": "Store all your passwords and personal info in your Bitwarden vault. We'll show you around." + }, + "vaultWelcomeDialogPrimaryCta": { + "message": "Start tour" + }, + "vaultWelcomeDialogDismissCta": { + "message": "Skip" + }, "sendPasswordHelperText": { "message": "Individuals will need to enter the password to view this Send", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." @@ -12948,5 +12998,23 @@ }, "paymentMethodUpdateError": { "message": "There was an error updating your payment method." + }, + "sendPasswordInvalidAskOwner": { + "message": "Invalid password. Ask the sender for the password needed to access this Send.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "sendExpiresOn": { + "message": "This Send expires at $TIME$ on $DATE$", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated.", + "placeholders": { + "time": { + "content": "$1", + "example": "10:00 AM" + }, + "date": { + "content": "$2", + "example": "Jan 1, 1970" + } + } } } diff --git a/apps/web/src/locales/sv/messages.json b/apps/web/src/locales/sv/messages.json index 3cb3116b684..77771bb166d 100644 --- a/apps/web/src/locales/sv/messages.json +++ b/apps/web/src/locales/sv/messages.json @@ -4337,6 +4337,15 @@ } } }, + "automaticallyConfirmedUserId": { + "message": "Automatically confirmed user $ID$.", + "placeholders": { + "id": { + "content": "$1", + "example": "John Smith" + } + } + }, "editedUserId": { "message": "Redigerade användaren $ID$.", "placeholders": { @@ -4596,29 +4605,26 @@ "generatingYourAccessIntelligence": { "message": "Genererar din Access Intelligence..." }, - "fetchingMemberData": { - "message": "Hämtar medlemsdata..." - }, - "analyzingPasswordHealth": { - "message": "Analyserar lösenordshälsa..." - }, - "calculatingRiskScores": { - "message": "Beräknar riskpoäng..." - }, - "generatingReportData": { - "message": "Genererar rapportdata..." - }, - "savingReport": { - "message": "Sparar rapport..." - }, - "compilingInsights": { - "message": "Sammanställer insikter..." - }, "loadingProgress": { "message": "Inläsningsförlopp" }, - "thisMightTakeFewMinutes": { - "message": "Detta kan ta några minuter." + "reviewingMemberData": { + "message": "Reviewing member data..." + }, + "analyzingPasswords": { + "message": "Analyzing passwords..." + }, + "calculatingRisks": { + "message": "Calculating risks..." + }, + "generatingReports": { + "message": "Generating reports..." + }, + "compilingInsightsProgress": { + "message": "Compiling insights..." + }, + "reportGenerationDone": { + "message": "Done!" }, "riskInsightsRunReport": { "message": "Kör rapport" @@ -5849,10 +5855,6 @@ "message": "Vet du inte lösenordet? Fråga avsändaren om lösenordet som behövs för att komma åt denna Send.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, - "sendHiddenByDefault": { - "message": "Denna Send är dold som standard. Du kan växla dess synlighet med hjälp av knappen nedan.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, "downloadAttachments": { "message": "Ladda ner bilagor" }, @@ -6145,6 +6147,21 @@ "autoConfirmCheckBoxLabel": { "message": "Jag accepterar dessa risker och policyuppdateringar" }, + "autoConfirmEnabledByAdmin": { + "message": "Turned on Automatic user confirmation setting" + }, + "autoConfirmDisabledByAdmin": { + "message": "Turned off Automatic user confirmation setting" + }, + "autoConfirmEnabledByPortal": { + "message": "Added Automatic user confirmation policy" + }, + "autoConfirmDisabledByPortal": { + "message": "Removed Automatic user confirmation policy" + }, + "system": { + "message": "System" + }, "personalOwnership": { "message": "Radera individuellt valv" }, @@ -7400,6 +7417,9 @@ "invalidVerificationCode": { "message": "Ogiltig verifieringskod" }, + "invalidEmailOrVerificationCode": { + "message": "Invalid email or verification code" + }, "keyConnectorDomain": { "message": "Key Connector-domän" }, @@ -12857,6 +12877,21 @@ "storageFullDescription": { "message": "Du har använt alla $GB$ GB av din krypterade lagring. För att fortsätta lagra filer, lägg till mer lagringsutrymme." }, + "extensionPromptHeading": { + "message": "Skaffa tillägget för enkel åtkomst till valv" + }, + "extensionPromptBody": { + "message": "Med webbläsartillägget installerat tar du Bitwarden överallt på nätet. Det fyller i lösenord, så att du kan logga in på dina konton med ett enda klick." + }, + "extensionPromptImageAlt": { + "message": "En webbläsare som visar Bitwarden-tillägget med autofyll objekt för den aktuella webbsidan." + }, + "skip": { + "message": "Hoppa över" + }, + "downloadExtension": { + "message": "Ladda ner tillägg" + }, "whoCanView": { "message": "Vem kan se" }, @@ -12869,6 +12904,9 @@ "enterMultipleEmailsSeparatedByComma": { "message": "Ange flera e-postadresser genom att separera dem med kommatecken." }, + "emailsRequiredChangeAccessType": { + "message": "Email verification requires at least one email address. To remove all emails, change the access type above." + }, "emailPlaceholder": { "message": "användare@bitwarden.com , användare@acme.com" }, @@ -12893,6 +12931,18 @@ "invalidSendPassword": { "message": "Ogiltigt Send-lösenord" }, + "vaultWelcomeDialogTitle": { + "message": "You're in! Welcome to Bitwarden" + }, + "vaultWelcomeDialogDescription": { + "message": "Store all your passwords and personal info in your Bitwarden vault. We'll show you around." + }, + "vaultWelcomeDialogPrimaryCta": { + "message": "Start tour" + }, + "vaultWelcomeDialogDismissCta": { + "message": "Skip" + }, "sendPasswordHelperText": { "message": "Individer måste ange lösenordet för att visa denna Send", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." @@ -12948,5 +12998,23 @@ }, "paymentMethodUpdateError": { "message": "There was an error updating your payment method." + }, + "sendPasswordInvalidAskOwner": { + "message": "Ogiltigt lösenord. Fråga avsändaren om lösenordet som behövs för att komma åt denna Send.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "sendExpiresOn": { + "message": "This Send expires at $TIME$ on $DATE$", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated.", + "placeholders": { + "time": { + "content": "$1", + "example": "10:00 AM" + }, + "date": { + "content": "$2", + "example": "Jan 1, 1970" + } + } } } diff --git a/apps/web/src/locales/ta/messages.json b/apps/web/src/locales/ta/messages.json index 3789574201c..757a8158097 100644 --- a/apps/web/src/locales/ta/messages.json +++ b/apps/web/src/locales/ta/messages.json @@ -4337,6 +4337,15 @@ } } }, + "automaticallyConfirmedUserId": { + "message": "Automatically confirmed user $ID$.", + "placeholders": { + "id": { + "content": "$1", + "example": "John Smith" + } + } + }, "editedUserId": { "message": "பயனர் $ID$ திருத்தப்பட்டார்.", "placeholders": { @@ -4596,29 +4605,26 @@ "generatingYourAccessIntelligence": { "message": "Generating your Access Intelligence..." }, - "fetchingMemberData": { - "message": "Fetching member data..." - }, - "analyzingPasswordHealth": { - "message": "Analyzing password health..." - }, - "calculatingRiskScores": { - "message": "Calculating risk scores..." - }, - "generatingReportData": { - "message": "Generating report data..." - }, - "savingReport": { - "message": "Saving report..." - }, - "compilingInsights": { - "message": "Compiling insights..." - }, "loadingProgress": { "message": "Loading progress" }, - "thisMightTakeFewMinutes": { - "message": "This might take a few minutes." + "reviewingMemberData": { + "message": "Reviewing member data..." + }, + "analyzingPasswords": { + "message": "Analyzing passwords..." + }, + "calculatingRisks": { + "message": "Calculating risks..." + }, + "generatingReports": { + "message": "Generating reports..." + }, + "compilingInsightsProgress": { + "message": "Compiling insights..." + }, + "reportGenerationDone": { + "message": "Done!" }, "riskInsightsRunReport": { "message": "Run report" @@ -5849,10 +5855,6 @@ "message": "கடவுச்சொல் தெரியவில்லையா? இந்த Send-ஐ அணுகத் தேவையான கடவுச்சொல்லை அனுப்புநரிடம் கேட்கவும்.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, - "sendHiddenByDefault": { - "message": "இந்த Send இயல்பாக மறைக்கப்பட்டுள்ளது. கீழே உள்ள பொத்தானைப் பயன்படுத்தி அதன் தெரிவுநிலையை மாற்றலாம்.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, "downloadAttachments": { "message": "இணைப்புகளைப் பதிவிறக்கவும்" }, @@ -6145,6 +6147,21 @@ "autoConfirmCheckBoxLabel": { "message": "I accept these risks and policy updates" }, + "autoConfirmEnabledByAdmin": { + "message": "Turned on Automatic user confirmation setting" + }, + "autoConfirmDisabledByAdmin": { + "message": "Turned off Automatic user confirmation setting" + }, + "autoConfirmEnabledByPortal": { + "message": "Added Automatic user confirmation policy" + }, + "autoConfirmDisabledByPortal": { + "message": "Removed Automatic user confirmation policy" + }, + "system": { + "message": "System" + }, "personalOwnership": { "message": "தனிப்பட்ட வால்ட்டை அகற்று" }, @@ -7400,6 +7417,9 @@ "invalidVerificationCode": { "message": "தவறான சரிபார்ப்புக் குறியீடு" }, + "invalidEmailOrVerificationCode": { + "message": "Invalid email or verification code" + }, "keyConnectorDomain": { "message": "கீ கனெக்டர் டொமைன்" }, @@ -12857,6 +12877,21 @@ "storageFullDescription": { "message": "You have used all $GB$ GB of your encrypted storage. To continue storing files, add more storage." }, + "extensionPromptHeading": { + "message": "Get the extension for easy vault access" + }, + "extensionPromptBody": { + "message": "With the browser extension installed, you'll take Bitwarden everywhere online. It'll fill in passwords, so you can log into your accounts with a single click." + }, + "extensionPromptImageAlt": { + "message": "A web browser showing the Bitwarden extension with autofill items for the current webpage." + }, + "skip": { + "message": "Skip" + }, + "downloadExtension": { + "message": "Download extension" + }, "whoCanView": { "message": "Who can view" }, @@ -12869,6 +12904,9 @@ "enterMultipleEmailsSeparatedByComma": { "message": "Enter multiple emails by separating with a comma." }, + "emailsRequiredChangeAccessType": { + "message": "Email verification requires at least one email address. To remove all emails, change the access type above." + }, "emailPlaceholder": { "message": "user@bitwarden.com , user@acme.com" }, @@ -12893,6 +12931,18 @@ "invalidSendPassword": { "message": "Invalid Send password" }, + "vaultWelcomeDialogTitle": { + "message": "You're in! Welcome to Bitwarden" + }, + "vaultWelcomeDialogDescription": { + "message": "Store all your passwords and personal info in your Bitwarden vault. We'll show you around." + }, + "vaultWelcomeDialogPrimaryCta": { + "message": "Start tour" + }, + "vaultWelcomeDialogDismissCta": { + "message": "Skip" + }, "sendPasswordHelperText": { "message": "Individuals will need to enter the password to view this Send", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." @@ -12948,5 +12998,23 @@ }, "paymentMethodUpdateError": { "message": "There was an error updating your payment method." + }, + "sendPasswordInvalidAskOwner": { + "message": "Invalid password. Ask the sender for the password needed to access this Send.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "sendExpiresOn": { + "message": "This Send expires at $TIME$ on $DATE$", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated.", + "placeholders": { + "time": { + "content": "$1", + "example": "10:00 AM" + }, + "date": { + "content": "$2", + "example": "Jan 1, 1970" + } + } } } diff --git a/apps/web/src/locales/te/messages.json b/apps/web/src/locales/te/messages.json index bc0e3b74cb6..4e6a8265ad6 100644 --- a/apps/web/src/locales/te/messages.json +++ b/apps/web/src/locales/te/messages.json @@ -4337,6 +4337,15 @@ } } }, + "automaticallyConfirmedUserId": { + "message": "Automatically confirmed user $ID$.", + "placeholders": { + "id": { + "content": "$1", + "example": "John Smith" + } + } + }, "editedUserId": { "message": "Edited user $ID$.", "placeholders": { @@ -4596,29 +4605,26 @@ "generatingYourAccessIntelligence": { "message": "Generating your Access Intelligence..." }, - "fetchingMemberData": { - "message": "Fetching member data..." - }, - "analyzingPasswordHealth": { - "message": "Analyzing password health..." - }, - "calculatingRiskScores": { - "message": "Calculating risk scores..." - }, - "generatingReportData": { - "message": "Generating report data..." - }, - "savingReport": { - "message": "Saving report..." - }, - "compilingInsights": { - "message": "Compiling insights..." - }, "loadingProgress": { "message": "Loading progress" }, - "thisMightTakeFewMinutes": { - "message": "This might take a few minutes." + "reviewingMemberData": { + "message": "Reviewing member data..." + }, + "analyzingPasswords": { + "message": "Analyzing passwords..." + }, + "calculatingRisks": { + "message": "Calculating risks..." + }, + "generatingReports": { + "message": "Generating reports..." + }, + "compilingInsightsProgress": { + "message": "Compiling insights..." + }, + "reportGenerationDone": { + "message": "Done!" }, "riskInsightsRunReport": { "message": "Run report" @@ -5849,10 +5855,6 @@ "message": "Don't know the password? Ask the sender for the password needed to access this Send.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, - "sendHiddenByDefault": { - "message": "This Send is hidden by default. You can toggle its visibility using the button below.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, "downloadAttachments": { "message": "Download attachments" }, @@ -6145,6 +6147,21 @@ "autoConfirmCheckBoxLabel": { "message": "I accept these risks and policy updates" }, + "autoConfirmEnabledByAdmin": { + "message": "Turned on Automatic user confirmation setting" + }, + "autoConfirmDisabledByAdmin": { + "message": "Turned off Automatic user confirmation setting" + }, + "autoConfirmEnabledByPortal": { + "message": "Added Automatic user confirmation policy" + }, + "autoConfirmDisabledByPortal": { + "message": "Removed Automatic user confirmation policy" + }, + "system": { + "message": "System" + }, "personalOwnership": { "message": "Remove individual vault" }, @@ -7400,6 +7417,9 @@ "invalidVerificationCode": { "message": "Invalid verification code" }, + "invalidEmailOrVerificationCode": { + "message": "Invalid email or verification code" + }, "keyConnectorDomain": { "message": "Key Connector domain" }, @@ -12857,6 +12877,21 @@ "storageFullDescription": { "message": "You have used all $GB$ GB of your encrypted storage. To continue storing files, add more storage." }, + "extensionPromptHeading": { + "message": "Get the extension for easy vault access" + }, + "extensionPromptBody": { + "message": "With the browser extension installed, you'll take Bitwarden everywhere online. It'll fill in passwords, so you can log into your accounts with a single click." + }, + "extensionPromptImageAlt": { + "message": "A web browser showing the Bitwarden extension with autofill items for the current webpage." + }, + "skip": { + "message": "Skip" + }, + "downloadExtension": { + "message": "Download extension" + }, "whoCanView": { "message": "Who can view" }, @@ -12869,6 +12904,9 @@ "enterMultipleEmailsSeparatedByComma": { "message": "Enter multiple emails by separating with a comma." }, + "emailsRequiredChangeAccessType": { + "message": "Email verification requires at least one email address. To remove all emails, change the access type above." + }, "emailPlaceholder": { "message": "user@bitwarden.com , user@acme.com" }, @@ -12893,6 +12931,18 @@ "invalidSendPassword": { "message": "Invalid Send password" }, + "vaultWelcomeDialogTitle": { + "message": "You're in! Welcome to Bitwarden" + }, + "vaultWelcomeDialogDescription": { + "message": "Store all your passwords and personal info in your Bitwarden vault. We'll show you around." + }, + "vaultWelcomeDialogPrimaryCta": { + "message": "Start tour" + }, + "vaultWelcomeDialogDismissCta": { + "message": "Skip" + }, "sendPasswordHelperText": { "message": "Individuals will need to enter the password to view this Send", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." @@ -12948,5 +12998,23 @@ }, "paymentMethodUpdateError": { "message": "There was an error updating your payment method." + }, + "sendPasswordInvalidAskOwner": { + "message": "Invalid password. Ask the sender for the password needed to access this Send.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "sendExpiresOn": { + "message": "This Send expires at $TIME$ on $DATE$", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated.", + "placeholders": { + "time": { + "content": "$1", + "example": "10:00 AM" + }, + "date": { + "content": "$2", + "example": "Jan 1, 1970" + } + } } } diff --git a/apps/web/src/locales/th/messages.json b/apps/web/src/locales/th/messages.json index eb6fa9011fc..2eb0a00f8fc 100644 --- a/apps/web/src/locales/th/messages.json +++ b/apps/web/src/locales/th/messages.json @@ -4337,6 +4337,15 @@ } } }, + "automaticallyConfirmedUserId": { + "message": "Automatically confirmed user $ID$.", + "placeholders": { + "id": { + "content": "$1", + "example": "John Smith" + } + } + }, "editedUserId": { "message": "Edited user $ID$.", "placeholders": { @@ -4596,29 +4605,26 @@ "generatingYourAccessIntelligence": { "message": "Generating your Access Intelligence..." }, - "fetchingMemberData": { - "message": "Fetching member data..." - }, - "analyzingPasswordHealth": { - "message": "Analyzing password health..." - }, - "calculatingRiskScores": { - "message": "Calculating risk scores..." - }, - "generatingReportData": { - "message": "Generating report data..." - }, - "savingReport": { - "message": "Saving report..." - }, - "compilingInsights": { - "message": "Compiling insights..." - }, "loadingProgress": { "message": "Loading progress" }, - "thisMightTakeFewMinutes": { - "message": "This might take a few minutes." + "reviewingMemberData": { + "message": "Reviewing member data..." + }, + "analyzingPasswords": { + "message": "Analyzing passwords..." + }, + "calculatingRisks": { + "message": "Calculating risks..." + }, + "generatingReports": { + "message": "Generating reports..." + }, + "compilingInsightsProgress": { + "message": "Compiling insights..." + }, + "reportGenerationDone": { + "message": "Done!" }, "riskInsightsRunReport": { "message": "Run report" @@ -5849,10 +5855,6 @@ "message": "Don't know the password? Ask the sender for the password needed to access this Send.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, - "sendHiddenByDefault": { - "message": "This Send is hidden by default. You can toggle its visibility using the button below.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, "downloadAttachments": { "message": "Download attachments" }, @@ -6145,6 +6147,21 @@ "autoConfirmCheckBoxLabel": { "message": "I accept these risks and policy updates" }, + "autoConfirmEnabledByAdmin": { + "message": "Turned on Automatic user confirmation setting" + }, + "autoConfirmDisabledByAdmin": { + "message": "Turned off Automatic user confirmation setting" + }, + "autoConfirmEnabledByPortal": { + "message": "Added Automatic user confirmation policy" + }, + "autoConfirmDisabledByPortal": { + "message": "Removed Automatic user confirmation policy" + }, + "system": { + "message": "System" + }, "personalOwnership": { "message": "Remove individual vault" }, @@ -7400,6 +7417,9 @@ "invalidVerificationCode": { "message": "Invalid verification code" }, + "invalidEmailOrVerificationCode": { + "message": "Invalid email or verification code" + }, "keyConnectorDomain": { "message": "Key Connector domain" }, @@ -12857,6 +12877,21 @@ "storageFullDescription": { "message": "You have used all $GB$ GB of your encrypted storage. To continue storing files, add more storage." }, + "extensionPromptHeading": { + "message": "Get the extension for easy vault access" + }, + "extensionPromptBody": { + "message": "With the browser extension installed, you'll take Bitwarden everywhere online. It'll fill in passwords, so you can log into your accounts with a single click." + }, + "extensionPromptImageAlt": { + "message": "A web browser showing the Bitwarden extension with autofill items for the current webpage." + }, + "skip": { + "message": "Skip" + }, + "downloadExtension": { + "message": "Download extension" + }, "whoCanView": { "message": "Who can view" }, @@ -12869,6 +12904,9 @@ "enterMultipleEmailsSeparatedByComma": { "message": "Enter multiple emails by separating with a comma." }, + "emailsRequiredChangeAccessType": { + "message": "Email verification requires at least one email address. To remove all emails, change the access type above." + }, "emailPlaceholder": { "message": "user@bitwarden.com , user@acme.com" }, @@ -12893,6 +12931,18 @@ "invalidSendPassword": { "message": "Invalid Send password" }, + "vaultWelcomeDialogTitle": { + "message": "You're in! Welcome to Bitwarden" + }, + "vaultWelcomeDialogDescription": { + "message": "Store all your passwords and personal info in your Bitwarden vault. We'll show you around." + }, + "vaultWelcomeDialogPrimaryCta": { + "message": "Start tour" + }, + "vaultWelcomeDialogDismissCta": { + "message": "Skip" + }, "sendPasswordHelperText": { "message": "Individuals will need to enter the password to view this Send", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." @@ -12948,5 +12998,23 @@ }, "paymentMethodUpdateError": { "message": "There was an error updating your payment method." + }, + "sendPasswordInvalidAskOwner": { + "message": "Invalid password. Ask the sender for the password needed to access this Send.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "sendExpiresOn": { + "message": "This Send expires at $TIME$ on $DATE$", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated.", + "placeholders": { + "time": { + "content": "$1", + "example": "10:00 AM" + }, + "date": { + "content": "$2", + "example": "Jan 1, 1970" + } + } } } diff --git a/apps/web/src/locales/tr/messages.json b/apps/web/src/locales/tr/messages.json index 53348da8697..260514462cb 100644 --- a/apps/web/src/locales/tr/messages.json +++ b/apps/web/src/locales/tr/messages.json @@ -4337,6 +4337,15 @@ } } }, + "automaticallyConfirmedUserId": { + "message": "Automatically confirmed user $ID$.", + "placeholders": { + "id": { + "content": "$1", + "example": "John Smith" + } + } + }, "editedUserId": { "message": "Kullanıcı düzenlendi: $ID$.", "placeholders": { @@ -4596,29 +4605,26 @@ "generatingYourAccessIntelligence": { "message": "Access Intelligence’ınız oluşturuluyor..." }, - "fetchingMemberData": { - "message": "Üye verileri getiriliyor..." - }, - "analyzingPasswordHealth": { - "message": "Parola sağlığı analiz ediliyor..." - }, - "calculatingRiskScores": { - "message": "Risk puanları hesaplanıyor..." - }, - "generatingReportData": { - "message": "Rapor verileri oluşturuluyor..." - }, - "savingReport": { - "message": "Rapor kaydediliyor..." - }, - "compilingInsights": { - "message": "İçgörüler derleniyor..." - }, "loadingProgress": { "message": "Yükleme ilerlemesi" }, - "thisMightTakeFewMinutes": { - "message": "Bu işlem birkaç dakika sürebilir." + "reviewingMemberData": { + "message": "Reviewing member data..." + }, + "analyzingPasswords": { + "message": "Analyzing passwords..." + }, + "calculatingRisks": { + "message": "Calculating risks..." + }, + "generatingReports": { + "message": "Generating reports..." + }, + "compilingInsightsProgress": { + "message": "Compiling insights..." + }, + "reportGenerationDone": { + "message": "Done!" }, "riskInsightsRunReport": { "message": "Raporu çalıştır" @@ -5849,10 +5855,6 @@ "message": "Parolayı bilmiyor musunuz? Bu Send'e erişmek için gereken parolayı dosyayı gönderen kişiye sorabilirsiniz.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, - "sendHiddenByDefault": { - "message": "Bu Send varsayılan olarak gizlidir. Aşağıdaki düğmeyi kullanarak görünürlüğünü değiştirebilirsiniz.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, "downloadAttachments": { "message": "Ekleri indir" }, @@ -6145,6 +6147,21 @@ "autoConfirmCheckBoxLabel": { "message": "Bu riskleri ve ilke güncellemelerini kabul ediyorum" }, + "autoConfirmEnabledByAdmin": { + "message": "Turned on Automatic user confirmation setting" + }, + "autoConfirmDisabledByAdmin": { + "message": "Turned off Automatic user confirmation setting" + }, + "autoConfirmEnabledByPortal": { + "message": "Added Automatic user confirmation policy" + }, + "autoConfirmDisabledByPortal": { + "message": "Removed Automatic user confirmation policy" + }, + "system": { + "message": "Sistem" + }, "personalOwnership": { "message": "Kişisel kasayı kaldır" }, @@ -7400,6 +7417,9 @@ "invalidVerificationCode": { "message": "Geçersiz doğrulama kodu" }, + "invalidEmailOrVerificationCode": { + "message": "E-posta veya doğrulama kodu geçersiz" + }, "keyConnectorDomain": { "message": "Key Connector alan adı" }, @@ -12857,6 +12877,21 @@ "storageFullDescription": { "message": "You have used all $GB$ GB of your encrypted storage. To continue storing files, add more storage." }, + "extensionPromptHeading": { + "message": "Get the extension for easy vault access" + }, + "extensionPromptBody": { + "message": "With the browser extension installed, you'll take Bitwarden everywhere online. It'll fill in passwords, so you can log into your accounts with a single click." + }, + "extensionPromptImageAlt": { + "message": "A web browser showing the Bitwarden extension with autofill items for the current webpage." + }, + "skip": { + "message": "Skip" + }, + "downloadExtension": { + "message": "Download extension" + }, "whoCanView": { "message": "Kim görebilir" }, @@ -12869,6 +12904,9 @@ "enterMultipleEmailsSeparatedByComma": { "message": "E-posta adreslerini virgülle ayırarak yazın." }, + "emailsRequiredChangeAccessType": { + "message": "E-posta doğrulaması için en az bir e-posta adresi gerekir. Tüm e-postaları silmek için yukarıdan erişim türünü değiştirin." + }, "emailPlaceholder": { "message": "kullanici@bitwarden.com , kullanici@acme.com" }, @@ -12893,6 +12931,18 @@ "invalidSendPassword": { "message": "Geçersiz Send parolası" }, + "vaultWelcomeDialogTitle": { + "message": "Bitwarden'a hoş geldiniz" + }, + "vaultWelcomeDialogDescription": { + "message": "Tüm parolalarınızı ve kişisel bilgilerinizi Bitwarden kasanızda saklayabilirsiniz. Size etrafı gezdirelim." + }, + "vaultWelcomeDialogPrimaryCta": { + "message": "Tura başla" + }, + "vaultWelcomeDialogDismissCta": { + "message": "Geç" + }, "sendPasswordHelperText": { "message": "Bu Send'i görmek isteyen kişilerin parola girmesi gerekecektir", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." @@ -12948,5 +12998,23 @@ }, "paymentMethodUpdateError": { "message": "Ödeme yönteminizi güncellerken bir hata oluştu." + }, + "sendPasswordInvalidAskOwner": { + "message": "Parola geçersiz. Bu Send'e erişmek için gereken parolayı dosyayı gönderen kişiye sorabilirsiniz.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "sendExpiresOn": { + "message": "Bu Send'in süresi $DATE$ $TIME$ tarihinde dolacaktır", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated.", + "placeholders": { + "time": { + "content": "$1", + "example": "10:00 AM" + }, + "date": { + "content": "$2", + "example": "Jan 1, 1970" + } + } } } diff --git a/apps/web/src/locales/uk/messages.json b/apps/web/src/locales/uk/messages.json index dcb5d26aa1f..7dc407ad5e6 100644 --- a/apps/web/src/locales/uk/messages.json +++ b/apps/web/src/locales/uk/messages.json @@ -4337,6 +4337,15 @@ } } }, + "automaticallyConfirmedUserId": { + "message": "Automatically confirmed user $ID$.", + "placeholders": { + "id": { + "content": "$1", + "example": "John Smith" + } + } + }, "editedUserId": { "message": "Користувача $ID$ змінено.", "placeholders": { @@ -4596,29 +4605,26 @@ "generatingYourAccessIntelligence": { "message": "Generating your Access Intelligence..." }, - "fetchingMemberData": { - "message": "Fetching member data..." - }, - "analyzingPasswordHealth": { - "message": "Analyzing password health..." - }, - "calculatingRiskScores": { - "message": "Calculating risk scores..." - }, - "generatingReportData": { - "message": "Generating report data..." - }, - "savingReport": { - "message": "Saving report..." - }, - "compilingInsights": { - "message": "Compiling insights..." - }, "loadingProgress": { "message": "Loading progress" }, - "thisMightTakeFewMinutes": { - "message": "This might take a few minutes." + "reviewingMemberData": { + "message": "Reviewing member data..." + }, + "analyzingPasswords": { + "message": "Analyzing passwords..." + }, + "calculatingRisks": { + "message": "Calculating risks..." + }, + "generatingReports": { + "message": "Generating reports..." + }, + "compilingInsightsProgress": { + "message": "Compiling insights..." + }, + "reportGenerationDone": { + "message": "Done!" }, "riskInsightsRunReport": { "message": "Run report" @@ -5849,10 +5855,6 @@ "message": "Не знаєте пароль? Попросіть його у відправника для отримання доступу.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, - "sendHiddenByDefault": { - "message": "Це відправлення типово приховане. Ви можете змінити його видимість кнопкою нижче.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, "downloadAttachments": { "message": "Завантажити вкладення" }, @@ -6145,6 +6147,21 @@ "autoConfirmCheckBoxLabel": { "message": "I accept these risks and policy updates" }, + "autoConfirmEnabledByAdmin": { + "message": "Turned on Automatic user confirmation setting" + }, + "autoConfirmDisabledByAdmin": { + "message": "Turned off Automatic user confirmation setting" + }, + "autoConfirmEnabledByPortal": { + "message": "Added Automatic user confirmation policy" + }, + "autoConfirmDisabledByPortal": { + "message": "Removed Automatic user confirmation policy" + }, + "system": { + "message": "System" + }, "personalOwnership": { "message": "Вилучити особисте сховище" }, @@ -7400,6 +7417,9 @@ "invalidVerificationCode": { "message": "Недійсний код підтвердження" }, + "invalidEmailOrVerificationCode": { + "message": "Invalid email or verification code" + }, "keyConnectorDomain": { "message": "Домен Key Connector" }, @@ -12857,6 +12877,21 @@ "storageFullDescription": { "message": "You have used all $GB$ GB of your encrypted storage. To continue storing files, add more storage." }, + "extensionPromptHeading": { + "message": "Get the extension for easy vault access" + }, + "extensionPromptBody": { + "message": "With the browser extension installed, you'll take Bitwarden everywhere online. It'll fill in passwords, so you can log into your accounts with a single click." + }, + "extensionPromptImageAlt": { + "message": "A web browser showing the Bitwarden extension with autofill items for the current webpage." + }, + "skip": { + "message": "Skip" + }, + "downloadExtension": { + "message": "Download extension" + }, "whoCanView": { "message": "Хто може переглядати" }, @@ -12869,6 +12904,9 @@ "enterMultipleEmailsSeparatedByComma": { "message": "Введіть декілька адрес е-пошти, розділяючи їх комою." }, + "emailsRequiredChangeAccessType": { + "message": "Email verification requires at least one email address. To remove all emails, change the access type above." + }, "emailPlaceholder": { "message": "user@bitwarden.com , user@acme.com" }, @@ -12893,6 +12931,18 @@ "invalidSendPassword": { "message": "Invalid Send password" }, + "vaultWelcomeDialogTitle": { + "message": "You're in! Welcome to Bitwarden" + }, + "vaultWelcomeDialogDescription": { + "message": "Store all your passwords and personal info in your Bitwarden vault. We'll show you around." + }, + "vaultWelcomeDialogPrimaryCta": { + "message": "Start tour" + }, + "vaultWelcomeDialogDismissCta": { + "message": "Skip" + }, "sendPasswordHelperText": { "message": "Individuals will need to enter the password to view this Send", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." @@ -12948,5 +12998,23 @@ }, "paymentMethodUpdateError": { "message": "There was an error updating your payment method." + }, + "sendPasswordInvalidAskOwner": { + "message": "Invalid password. Ask the sender for the password needed to access this Send.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "sendExpiresOn": { + "message": "This Send expires at $TIME$ on $DATE$", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated.", + "placeholders": { + "time": { + "content": "$1", + "example": "10:00 AM" + }, + "date": { + "content": "$2", + "example": "Jan 1, 1970" + } + } } } diff --git a/apps/web/src/locales/vi/messages.json b/apps/web/src/locales/vi/messages.json index 64d0703bc07..3671c9e8ab5 100644 --- a/apps/web/src/locales/vi/messages.json +++ b/apps/web/src/locales/vi/messages.json @@ -4337,6 +4337,15 @@ } } }, + "automaticallyConfirmedUserId": { + "message": "Automatically confirmed user $ID$.", + "placeholders": { + "id": { + "content": "$1", + "example": "John Smith" + } + } + }, "editedUserId": { "message": "Người dùng $ID$ đã được chỉnh sửa.", "placeholders": { @@ -4596,29 +4605,26 @@ "generatingYourAccessIntelligence": { "message": "Đang tạo Access Intelligence của bạn..." }, - "fetchingMemberData": { - "message": "Đang lấy dữ liệu thành viên..." - }, - "analyzingPasswordHealth": { - "message": "Đang phân tích độ mạnh mật khẩu..." - }, - "calculatingRiskScores": { - "message": "Đang tính điểm rủi ro..." - }, - "generatingReportData": { - "message": "Đang tạo dữ liệu báo cáo..." - }, - "savingReport": { - "message": "Đang lưu báo cáo..." - }, - "compilingInsights": { - "message": "Đang biên soạn thông tin chi tiết..." - }, "loadingProgress": { "message": "Đang tải tiến trình" }, - "thisMightTakeFewMinutes": { - "message": "Quá trình này có thể mất vài phút." + "reviewingMemberData": { + "message": "Reviewing member data..." + }, + "analyzingPasswords": { + "message": "Analyzing passwords..." + }, + "calculatingRisks": { + "message": "Calculating risks..." + }, + "generatingReports": { + "message": "Generating reports..." + }, + "compilingInsightsProgress": { + "message": "Compiling insights..." + }, + "reportGenerationDone": { + "message": "Done!" }, "riskInsightsRunReport": { "message": "Chạy báo cáo" @@ -5849,10 +5855,6 @@ "message": "Không biết mật khẩu? Hãy yêu cầu người gửi cung cấp mật khẩu cần thiết để truy cập vào Send này.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, - "sendHiddenByDefault": { - "message": "Send này sẽ bị ẩn theo mặc định. Bạn có thể bật/tắt tính năng này bằng cách nhấn vào nút bên dưới.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, "downloadAttachments": { "message": "Tải xuống tập tin đính kèm" }, @@ -6145,6 +6147,21 @@ "autoConfirmCheckBoxLabel": { "message": "Tôi chấp nhận những rủi ro và cập nhật chính sách này" }, + "autoConfirmEnabledByAdmin": { + "message": "Turned on Automatic user confirmation setting" + }, + "autoConfirmDisabledByAdmin": { + "message": "Turned off Automatic user confirmation setting" + }, + "autoConfirmEnabledByPortal": { + "message": "Added Automatic user confirmation policy" + }, + "autoConfirmDisabledByPortal": { + "message": "Removed Automatic user confirmation policy" + }, + "system": { + "message": "System" + }, "personalOwnership": { "message": "Xóa kho lưu trữ riêng lẻ" }, @@ -7400,6 +7417,9 @@ "invalidVerificationCode": { "message": "Mã xác minh không đúng" }, + "invalidEmailOrVerificationCode": { + "message": "Invalid email or verification code" + }, "keyConnectorDomain": { "message": "Tên miền Key Connector" }, @@ -12857,6 +12877,21 @@ "storageFullDescription": { "message": "You have used all $GB$ GB of your encrypted storage. To continue storing files, add more storage." }, + "extensionPromptHeading": { + "message": "Get the extension for easy vault access" + }, + "extensionPromptBody": { + "message": "With the browser extension installed, you'll take Bitwarden everywhere online. It'll fill in passwords, so you can log into your accounts with a single click." + }, + "extensionPromptImageAlt": { + "message": "A web browser showing the Bitwarden extension with autofill items for the current webpage." + }, + "skip": { + "message": "Skip" + }, + "downloadExtension": { + "message": "Download extension" + }, "whoCanView": { "message": "Who can view" }, @@ -12869,6 +12904,9 @@ "enterMultipleEmailsSeparatedByComma": { "message": "Enter multiple emails by separating with a comma." }, + "emailsRequiredChangeAccessType": { + "message": "Email verification requires at least one email address. To remove all emails, change the access type above." + }, "emailPlaceholder": { "message": "user@bitwarden.com , user@acme.com" }, @@ -12893,6 +12931,18 @@ "invalidSendPassword": { "message": "Invalid Send password" }, + "vaultWelcomeDialogTitle": { + "message": "You're in! Welcome to Bitwarden" + }, + "vaultWelcomeDialogDescription": { + "message": "Store all your passwords and personal info in your Bitwarden vault. We'll show you around." + }, + "vaultWelcomeDialogPrimaryCta": { + "message": "Start tour" + }, + "vaultWelcomeDialogDismissCta": { + "message": "Skip" + }, "sendPasswordHelperText": { "message": "Individuals will need to enter the password to view this Send", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." @@ -12948,5 +12998,23 @@ }, "paymentMethodUpdateError": { "message": "There was an error updating your payment method." + }, + "sendPasswordInvalidAskOwner": { + "message": "Invalid password. Ask the sender for the password needed to access this Send.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "sendExpiresOn": { + "message": "This Send expires at $TIME$ on $DATE$", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated.", + "placeholders": { + "time": { + "content": "$1", + "example": "10:00 AM" + }, + "date": { + "content": "$2", + "example": "Jan 1, 1970" + } + } } } diff --git a/apps/web/src/locales/zh_CN/messages.json b/apps/web/src/locales/zh_CN/messages.json index 47c732a5a32..ff23e62c5d8 100644 --- a/apps/web/src/locales/zh_CN/messages.json +++ b/apps/web/src/locales/zh_CN/messages.json @@ -3339,13 +3339,13 @@ "message": "您的订阅已恢复。" }, "resubscribe": { - "message": "Resubscribe" + "message": "重新订阅" }, "yourSubscriptionIsExpired": { - "message": "Your subscription is expired" + "message": "您的订阅已过期" }, "yourSubscriptionIsCanceled": { - "message": "Your subscription is canceled" + "message": "您的订阅已取消" }, "cancelConfirmation": { "message": "确定要取消吗?在本次计费周期结束后,您将无法使用此订阅的所有功能。" @@ -4337,6 +4337,15 @@ } } }, + "automaticallyConfirmedUserId": { + "message": "自动确认了用户 $ID$。", + "placeholders": { + "id": { + "content": "$1", + "example": "John Smith" + } + } + }, "editedUserId": { "message": "编辑了用户 $ID$。", "placeholders": { @@ -4596,29 +4605,26 @@ "generatingYourAccessIntelligence": { "message": "正在生成 Access Intelligence..." }, - "fetchingMemberData": { - "message": "正在获取成员数据..." - }, - "analyzingPasswordHealth": { - "message": "正在分析密码健康度..." - }, - "calculatingRiskScores": { - "message": "正在计算风险评分..." - }, - "generatingReportData": { - "message": "正在生成报告数据..." - }, - "savingReport": { - "message": "正在保存报告..." - }, - "compilingInsights": { - "message": "正在编译洞察..." - }, "loadingProgress": { "message": "加载进度" }, - "thisMightTakeFewMinutes": { - "message": "这可能需要几分钟时间。" + "reviewingMemberData": { + "message": "正在审查成员数据..." + }, + "analyzingPasswords": { + "message": "正在分析密码..." + }, + "calculatingRisks": { + "message": "正在计算风险..." + }, + "generatingReports": { + "message": "正在生成报告..." + }, + "compilingInsightsProgress": { + "message": "正在编译洞察..." + }, + "reportGenerationDone": { + "message": "完成!" }, "riskInsightsRunReport": { "message": "运行报告" @@ -5849,10 +5855,6 @@ "message": "不知道密码吗?请向发送者索取访问此 Send 所需的密码。", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, - "sendHiddenByDefault": { - "message": "此 Send 默认隐藏。您可以使用下方的按钮切换其可见性。", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, "downloadAttachments": { "message": "下载附件" }, @@ -6145,6 +6147,21 @@ "autoConfirmCheckBoxLabel": { "message": "我接受这些风险和策略更新" }, + "autoConfirmEnabledByAdmin": { + "message": "启用了自动用户确认设置" + }, + "autoConfirmDisabledByAdmin": { + "message": "停用了自动用户确认设置" + }, + "autoConfirmEnabledByPortal": { + "message": "添加了自动用户确认策略" + }, + "autoConfirmDisabledByPortal": { + "message": "禁用了自动用户确认策略" + }, + "system": { + "message": "系统" + }, "personalOwnership": { "message": "禁用个人密码库" }, @@ -6439,7 +6456,7 @@ "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "verifyYourEmailToViewThisSend": { - "message": "Verify your email to view this Send", + "message": "验证您的电子邮箱以查看此 Send", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "viewSendHiddenEmailWarning": { @@ -6744,7 +6761,7 @@ "message": "1 份邀请未发送" }, "bulkReinviteFailureDescription": { - "message": "向 $TOTAL$ 位成员中的 $COUNT$ 位发送邀请时发生错误。请尝试重新发送,如果问题仍然存在,", + "message": "向 $TOTAL$ 位成员中的 $COUNT$ 位发送邀请时发生错误。请尝试再次发送,如果问题仍然存在,", "placeholders": { "count": { "content": "$1", @@ -7400,6 +7417,9 @@ "invalidVerificationCode": { "message": "无效的验证码" }, + "invalidEmailOrVerificationCode": { + "message": "无效的电子邮箱或验证码" + }, "keyConnectorDomain": { "message": "Key Connector 域名" }, @@ -9151,7 +9171,7 @@ "message": "查看全部" }, "showingPortionOfTotal": { - "message": "显示 $PORTION$ / $TOTAL$", + "message": "显示 $TOTAL$ 中的 $PORTION$", "placeholders": { "portion": { "content": "$1", @@ -11910,10 +11930,10 @@ "message": "已归档的项目将显示在此处,并将被排除在一般搜索结果和自动填充建议之外。" }, "itemArchiveToast": { - "message": "Item archived" + "message": "项目已归档" }, "itemUnarchivedToast": { - "message": "Item unarchived" + "message": "项目已取消归档" }, "bulkArchiveItems": { "message": "项目已归档" @@ -12857,6 +12877,21 @@ "storageFullDescription": { "message": "您已使用了全部的 $GB$ GB 加密存储空间。要继续存储文件,请添加更多存储空间。" }, + "extensionPromptHeading": { + "message": "获取扩展以便轻松访问密码库" + }, + "extensionPromptBody": { + "message": "安装浏览器扩展后,您可以随时随地在线使用 Bitwarden。它会自动填写密码,只需单击一下即可登录您的账户。" + }, + "extensionPromptImageAlt": { + "message": "A web browser showing the Bitwarden extension with autofill items for the current webpage." + }, + "skip": { + "message": "跳过" + }, + "downloadExtension": { + "message": "下载扩展" + }, "whoCanView": { "message": "谁可以查看" }, @@ -12869,6 +12904,9 @@ "enterMultipleEmailsSeparatedByComma": { "message": "输入多个电子邮箱(使用逗号分隔)。" }, + "emailsRequiredChangeAccessType": { + "message": "电子邮箱验证要求至少有一个电子邮箱地址。要移除所有电子邮箱,请更改上面的访问类型。" + }, "emailPlaceholder": { "message": "user@bitwarden.com, user@acme.com" }, @@ -12893,6 +12931,18 @@ "invalidSendPassword": { "message": "无效的 Send 密码" }, + "vaultWelcomeDialogTitle": { + "message": "您已成功加入!欢迎使用 Bitwarden" + }, + "vaultWelcomeDialogDescription": { + "message": "将您的所有密码和个人信息存储在你的 Bitwarden 密码库中。我们将带您熟悉一下。" + }, + "vaultWelcomeDialogPrimaryCta": { + "message": "开始导览" + }, + "vaultWelcomeDialogDismissCta": { + "message": "跳过" + }, "sendPasswordHelperText": { "message": "个人需要输入密码才能查看此 Send", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." @@ -12948,5 +12998,23 @@ }, "paymentMethodUpdateError": { "message": "更新您的付款方式时出错。" + }, + "sendPasswordInvalidAskOwner": { + "message": "无效的密码。请向发送者索取访问此 Send 所需的密码。", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "sendExpiresOn": { + "message": "此 Send 有效期至 $DATE$ $TIME$", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated.", + "placeholders": { + "time": { + "content": "$1", + "example": "10:00 AM" + }, + "date": { + "content": "$2", + "example": "Jan 1, 1970" + } + } } } diff --git a/apps/web/src/locales/zh_TW/messages.json b/apps/web/src/locales/zh_TW/messages.json index c006b37d612..31099edc763 100644 --- a/apps/web/src/locales/zh_TW/messages.json +++ b/apps/web/src/locales/zh_TW/messages.json @@ -2803,7 +2803,7 @@ "message": "用戶端 ID" }, "twoFactorDuoClientSecret": { - "message": "用戶端秘密" + "message": "用戶端機密" }, "twoFactorDuoApiHostname": { "message": "API 主機名稱" @@ -4337,6 +4337,15 @@ } } }, + "automaticallyConfirmedUserId": { + "message": "Automatically confirmed user $ID$.", + "placeholders": { + "id": { + "content": "$1", + "example": "John Smith" + } + } + }, "editedUserId": { "message": "已編輯使用者 $ID$。", "placeholders": { @@ -4596,29 +4605,26 @@ "generatingYourAccessIntelligence": { "message": "正在產生您的 Access Intelligence……" }, - "fetchingMemberData": { - "message": "正在擷取成員資料…" - }, - "analyzingPasswordHealth": { - "message": "正在分析密碼安全狀況…" - }, - "calculatingRiskScores": { - "message": "正在計算風險分數…" - }, - "generatingReportData": { - "message": "正在產生報告資料..." - }, - "savingReport": { - "message": "正在儲存報告..." - }, - "compilingInsights": { - "message": "正在整理洞察結果…" - }, "loadingProgress": { "message": "載入進度中" }, - "thisMightTakeFewMinutes": { - "message": "這可能需要幾分鐘。" + "reviewingMemberData": { + "message": "Reviewing member data..." + }, + "analyzingPasswords": { + "message": "Analyzing passwords..." + }, + "calculatingRisks": { + "message": "Calculating risks..." + }, + "generatingReports": { + "message": "Generating reports..." + }, + "compilingInsightsProgress": { + "message": "Compiling insights..." + }, + "reportGenerationDone": { + "message": "Done!" }, "riskInsightsRunReport": { "message": "執行報告" @@ -5849,10 +5855,6 @@ "message": "不知道密碼?請向此 Send 的寄件者索取密碼。", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, - "sendHiddenByDefault": { - "message": "此 Send 預設為隱藏。您可使用下方的按鈕切換其可見度。", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, "downloadAttachments": { "message": "下載附件" }, @@ -6145,6 +6147,21 @@ "autoConfirmCheckBoxLabel": { "message": "我接受這些風險與原則更新" }, + "autoConfirmEnabledByAdmin": { + "message": "Turned on Automatic user confirmation setting" + }, + "autoConfirmDisabledByAdmin": { + "message": "Turned off Automatic user confirmation setting" + }, + "autoConfirmEnabledByPortal": { + "message": "Added Automatic user confirmation policy" + }, + "autoConfirmDisabledByPortal": { + "message": "Removed Automatic user confirmation policy" + }, + "system": { + "message": "System" + }, "personalOwnership": { "message": "停用個人密碼庫" }, @@ -7400,6 +7417,9 @@ "invalidVerificationCode": { "message": "無效的驗證碼" }, + "invalidEmailOrVerificationCode": { + "message": "Invalid email or verification code" + }, "keyConnectorDomain": { "message": "Key Connector 網域" }, @@ -9791,7 +9811,7 @@ } }, "secretsManagerForPlanDesc": { - "message": "提供工程與 DevOps 團隊一套在軟體開發生命週期中,管理秘密資訊的功能。" + "message": "提供工程與 DevOps 團隊一套在軟體開發生命週期中,管理機密資訊的功能。" }, "free2PersonOrganization": { "message": "免費的 2 人組織" @@ -9833,7 +9853,7 @@ "message": "訂閲機密管理員" }, "addSecretsManagerUpgradeDesc": { - "message": "將機密管理員加入您的升級方案,來維持先前方案建立的秘密資訊的存取權限。" + "message": "將機密管理員加入您的升級方案,來維持先前方案建立的機密資訊的存取權限。" }, "additionalServiceAccounts": { "message": "額外服務帳戶" @@ -10906,7 +10926,7 @@ "message": "已驗證" }, "viewSecret": { - "message": "檢視秘密" + "message": "檢視機密" }, "noClients": { "message": "沒有可列出的客戶" @@ -11910,7 +11930,7 @@ "message": "封存的項目會顯示在此處,且不會出現在一般搜尋結果或自動填入建議中。" }, "itemArchiveToast": { - "message": "Item archived" + "message": "項目已封存" }, "itemUnarchivedToast": { "message": "Item unarchived" @@ -12857,6 +12877,21 @@ "storageFullDescription": { "message": "您已用完全部 $GB$ GB 的加密儲存空間。如需繼續儲存檔案,請增加儲存空間。" }, + "extensionPromptHeading": { + "message": "Get the extension for easy vault access" + }, + "extensionPromptBody": { + "message": "With the browser extension installed, you'll take Bitwarden everywhere online. It'll fill in passwords, so you can log into your accounts with a single click." + }, + "extensionPromptImageAlt": { + "message": "A web browser showing the Bitwarden extension with autofill items for the current webpage." + }, + "skip": { + "message": "Skip" + }, + "downloadExtension": { + "message": "Download extension" + }, "whoCanView": { "message": "誰可以檢視" }, @@ -12869,6 +12904,9 @@ "enterMultipleEmailsSeparatedByComma": { "message": "請以逗號分隔輸入多個電子郵件地址。" }, + "emailsRequiredChangeAccessType": { + "message": "Email verification requires at least one email address. To remove all emails, change the access type above." + }, "emailPlaceholder": { "message": "user@bitwarden.com , user@acme.com" }, @@ -12893,6 +12931,18 @@ "invalidSendPassword": { "message": "Send 密碼無效" }, + "vaultWelcomeDialogTitle": { + "message": "You're in! Welcome to Bitwarden" + }, + "vaultWelcomeDialogDescription": { + "message": "Store all your passwords and personal info in your Bitwarden vault. We'll show you around." + }, + "vaultWelcomeDialogPrimaryCta": { + "message": "Start tour" + }, + "vaultWelcomeDialogDismissCta": { + "message": "Skip" + }, "sendPasswordHelperText": { "message": "對方必須輸入密碼才能檢視此 Send", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." @@ -12948,5 +12998,23 @@ }, "paymentMethodUpdateError": { "message": "There was an error updating your payment method." + }, + "sendPasswordInvalidAskOwner": { + "message": "Invalid password. Ask the sender for the password needed to access this Send.", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." + }, + "sendExpiresOn": { + "message": "This Send expires at $TIME$ on $DATE$", + "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated.", + "placeholders": { + "time": { + "content": "$1", + "example": "10:00 AM" + }, + "date": { + "content": "$2", + "example": "Jan 1, 1970" + } + } } } diff --git a/bitwarden_license/bit-common/src/dirt/docs/README.md b/bitwarden_license/bit-common/src/dirt/docs/README.md new file mode 100644 index 00000000000..f07f5c8b44c --- /dev/null +++ b/bitwarden_license/bit-common/src/dirt/docs/README.md @@ -0,0 +1,73 @@ +# DIRT Team Documentation + +**Location:** `bitwarden_license/bit-common/src/dirt/docs/` +**Purpose:** Overview of DIRT team documentation with navigation to detailed guides + +--- + +## 🎯 Start Here + +**New to the DIRT team?** → [Getting Started](./getting-started.md) + +**Looking for something specific?** + +- **"What should I read for my task?"** → [Getting Started](./getting-started.md) +- **"How are docs organized?"** → [Documentation Structure](./documentation-structure.md) +- **"How do I implement a feature?"** → [Playbooks](./playbooks/) +- **"What are the coding standards?"** → [Standards](./standards/) +- **"How do services integrate with components?"** → [Integration Guide](./integration-guide.md) + +--- + +## 📁 What's in This Folder + +| Document/Folder | Purpose | +| -------------------------------------------------------------- | ------------------------------------------------- | +| **[getting-started.md](./getting-started.md)** | Navigation hub - what to read for your task | +| **[documentation-structure.md](./documentation-structure.md)** | Complete structure guide - how docs are organized | +| **[integration-guide.md](./integration-guide.md)** | Service ↔ Component integration patterns | +| **[playbooks/](./playbooks/)** | Step-by-step implementation guides | +| **[standards/](./standards/)** | Team coding and documentation standards | +| **[access-intelligence/](./access-intelligence/)** | Migration guides and architecture comparisons | + +--- + +## 🏗️ DIRT Team Features + +The DIRT team (Data, Insights, Reporting & Tooling) owns: + +- **Access Intelligence** - Organization security reporting and password health +- **Organization Integrations** - Third-party integrations +- **External Reports** - Organization reports (weak passwords, member access, etc.) +- **Phishing Detection** - Browser-based phishing detection + +**Documentation is organized by package:** + +- **bit-common** - Platform-agnostic services (work on all platforms) +- **bit-web** - Angular web components (web client only) +- **bit-browser** - Browser extension components + +For detailed feature documentation locations, see [Getting Started](./getting-started.md). + +--- + +## 📝 Creating New Documentation + +**Before creating new docs, follow these steps:** + +1. **Read the standards:** [Documentation Standards](./standards/documentation-standards.md) +2. **Check for overlaps:** Review existing docs to avoid duplication +3. **Follow the playbook:** [Documentation Playbook](./playbooks/documentation-playbook.md) +4. **Update navigation:** Add to [getting-started.md](./getting-started.md) if it's a primary entry point +5. **Update this README:** If adding a new category or top-level document + +**For detailed guidance on where to place docs, see:** + +- [Documentation Standards § Document Location Rules](./standards/documentation-standards.md#document-location-rules) +- [Documentation Structure](./documentation-structure.md) + +--- + +**Document Version:** 1.0 +**Last Updated:** 2026-02-17 +**Maintainer:** DIRT Team diff --git a/bitwarden_license/bit-common/src/dirt/docs/documentation-structure.md b/bitwarden_license/bit-common/src/dirt/docs/documentation-structure.md new file mode 100644 index 00000000000..7d7e20b5d31 --- /dev/null +++ b/bitwarden_license/bit-common/src/dirt/docs/documentation-structure.md @@ -0,0 +1,277 @@ +# DIRT Team Documentation Structure + +**Purpose:** Navigation guide for all DIRT team documentation organized by team/feature hierarchy + +--- + +## 📁 Documentation Organization + +DIRT team documentation follows a **team/feature** hierarchy organized across multiple locations based on separation of concerns: + +### Team-Level Documentation + +**Location:** `bitwarden_license/bit-common/src/dirt/docs/` + +**Scope:** Applies to all DIRT features (Access Intelligence, Phishing Detection, etc.) + +**Contains:** + +- Team playbooks (service, component, documentation) +- Team coding standards +- Integration guides +- Getting started guide + +### Feature-Level Documentation + +**Pattern:** Feature docs live **next to the feature code**, not in the team `docs/` folder. + +**Location:** `dirt/[feature]/docs/` + +**Examples:** + +- **Access Intelligence:** `dirt/access-intelligence/v2/docs/` (or `dirt/access-intelligence/docs/` for current version) +- **Phishing Detection (future):** `dirt/phishing-detection/docs/` + +**Feature docs contain:** + +- Feature-specific architecture +- Feature-specific implementation guides +- Feature-specific patterns + +**Exception:** Migration/transition documentation can live in team `docs/` as **team-level knowledge**. Example: `docs/access-intelligence/` contains migration guides from v1 to v2, which is team-level context about the transition, not feature-specific architecture. + +### 1. Services & Architecture (Platform-Agnostic) + +**Pattern:** `bitwarden_license/bit-common/src/dirt/[feature]/docs/` + +**Purpose:** Feature-specific documentation lives next to the feature code + +**Example for Access Intelligence:** + +- Location: `dirt/access-intelligence/v2/docs/` (for v2 architecture) +- Contains: Architecture docs, implementation guides specific to that version + +**Note:** Team-level migration docs may live in `docs/access-intelligence/` as team knowledge about the transition between versions. + +### 2. Components (Angular-Specific) + +**Pattern:** `bitwarden_license/bit-web/src/app/dirt/[feature]/docs/` + +**Purpose:** Angular-specific UI components for web client only + +**Example for Access Intelligence:** + +- Location: `dirt/access-intelligence/docs/` +- Contains: Component inventory, migration guides, Storybook + +--- + +## 🎯 Where to Start? + +**For navigation guidance (what to read), see:** [getting-started.md](./getting-started.md) + +This document focuses on **how** the documentation is organized, not **what** to read. + +--- + +## 🗂️ Complete File Structure + +``` +# ============================================================================ +# SERVICES & ARCHITECTURE (bit-common) +# Platform-agnostic - Used by web, desktop, browser, CLI +# ============================================================================ + +bitwarden_license/bit-common/src/dirt/ +├── docs/ ← TEAM-LEVEL documentation only +│ ├── README.md ← Team docs overview +│ ├── getting-started.md ← Entry point for team +│ ├── documentation-structure.md ← This file +│ ├── integration-guide.md ← Service ↔ Component integration +│ │ +│ ├── playbooks/ ← Team playbooks (service, component, docs) +│ │ └── README.md ← Playbook navigation +│ │ +│ ├── standards/ ← Team coding standards +│ │ └── standards.md ← Core standards +│ │ +│ └── access-intelligence/ ← EXCEPTION: Migration guides (team knowledge) +│ ├── README.md ← Migration overview +│ ├── ... ← Migration analysis files +│ ├── architecture/ ← Migration architecture comparison +│ │ └── ... ← Architecture comparison files +│ └── implementation/ ← Implementation guides +│ └── ... ← Integration guides +│ +└── [feature]/ ← FEATURE CODE + FEATURE DOCS + └── docs/ ← Feature-specific documentation + ├── README.md ← Feature docs navigation + ├── architecture/ ← Feature architecture (lives with code) + │ └── ... ← Architecture files + └── implementation/ ← Feature implementation guides + └── ... ← Implementation guide files + +# Example for Access Intelligence v2: +bitwarden_license/bit-common/src/dirt/access-intelligence/ +├── v2/ ← V2 implementation +│ ├── services/ ← V2 services +│ ├── models/ ← V2 models +│ └── docs/ ← V2-SPECIFIC documentation +│ ├── README.md ← V2 docs overview +│ ├── architecture/ ← V2 architecture +│ │ └── ... ← Architecture files +│ └── implementation/ ← V2 implementation guides +│ └── ... ← Implementation guide files +└── v1/ ← V1 implementation (legacy) + +# ============================================================================ +# COMPONENTS (bit-web) +# Angular-specific - Web client only +# ============================================================================ + +bitwarden_license/bit-web/src/app/dirt/[feature]/ +├── docs/ ← Component documentation +│ └── README.md ← Component docs navigation +├── [component folders]/ ← Angular components +└── v2/ ← V2 components (if applicable) + +# Example for Access Intelligence: +bitwarden_license/bit-web/src/app/dirt/access-intelligence/ +├── docs/ ← Component documentation +│ ├── README.md ← Component docs navigation +│ └── ... ← Component guides +├── [components]/ ← Angular components +└── v2/ ← V2 components (if applicable) + └── ... ← V2 component files +``` + +--- + +## 🔄 When to Update This Structure + +Update this document when: + +- [ ] Adding new documentation categories +- [ ] Changing file locations +- [ ] Restructuring documentation organization + +--- + +## 📝 Architecture Decisions + +**Where decisions are tracked:** + +- **Company-wide ADRs:** Stored in the `contributing-docs` repository +- **Feature-specific decisions:** Tracked in Confluence (link to be added) +- **Local decision notes (optional):** `~/Documents/bitwarden-notes/dirt/decisions/[feature]/` for personal reference before moving to Confluence + - Example: `~/Documents/bitwarden-notes/dirt/decisions/access-intelligence/` + +**What goes in repo architecture docs:** + +- Current architecture state +- Migration plans and roadmaps +- Technical constraints +- Implementation patterns + +**What goes in Confluence:** + +- Decision discussions and rationale +- Alternative approaches considered +- Stakeholder input +- Links to Slack discussions + +--- + +## ✏️ Creating New Documentation + +**Before creating new documentation, see:** [docs/README.md](./README.md) § Documentation Best Practices + +**Key principles:** + +- **Single responsibility** - Each document should answer one question +- **Check for overlaps** - Read related docs first +- **Follow naming conventions** - See [documentation-standards.md](./standards/documentation-standards.md) +- **Cross-reference standards** - See [documentation-standards.md § Cross-Reference Standards](./standards/documentation-standards.md#cross-reference-standards) +- **Update navigation** - Add to getting-started.md if it's a primary entry point + +--- + +## 📊 Why This Structure? + +### Documentation Placement Principles + +**Team-Level Documentation (`docs/`):** + +- Applies to all DIRT features +- Playbooks, standards, getting-started guides +- Migration guides and transition documentation (team knowledge about rewrites) +- Cross-feature integration patterns + +**Feature-Level Documentation (`dirt/[feature]/docs/`):** + +- Lives **next to the feature code** +- Feature-specific architecture +- Version-specific implementation details +- Feature-specific patterns + +**Rationale:** + +- **Discoverability:** Architecture docs are found where the code lives +- **Versioning:** v1 and v2 can have separate docs directories +- **Maintainability:** Update feature docs without touching team docs +- **Clarity:** Clear separation between "what applies to all features" vs "what applies to this feature" + +### Separation of Concerns + +**Platform-Agnostic (bit-common):** + +- Services work on all platforms (web, desktop, browser, CLI) +- Domain models are platform-independent +- Architecture decisions affect all clients +- **Feature docs live with feature code:** `dirt/[feature]/docs/` + +**Angular-Specific (bit-web):** + +- Components only used in web client +- Storybook is web-only +- Angular-specific patterns (OnPush, Signals, etc.) +- **Component docs live with components:** `dirt/[feature]/docs/` + +### Benefits + +1. **Clarity:** Developers know where to look based on what they're working on +2. **Separation:** Team docs vs feature docs, Angular code vs platform-agnostic code +3. **Discoverability:** Feature docs are near feature code +4. **Maintainability:** Easier to update feature docs without affecting team docs +5. **Scalability:** Can add versioned docs (v1/, v2/) next to versioned code +6. **Migration clarity:** Team `docs/` can hold migration guides while feature `docs/` hold version-specific architecture + +--- + +## 🆘 Need Help? + +### Can't Find Documentation? + +1. **Start with getting-started.md:** [getting-started.md](./getting-started.md) + - Navigation hub for all DIRT team documentation + - Links to all major documentation categories + +2. **Check README files:** + - [Team Documentation README](./README.md) + - [Component README](/bitwarden_license/bit-web/src/app/dirt/access-intelligence/docs/README.md) + +3. **Check feature-specific docs:** + - Look in `dirt/[feature]/docs/` next to the feature code + - Example: `dirt/access-intelligence/v2/docs/` + +### Links Broken? + +- Check if file was moved +- Update cross-references following [documentation-standards.md § Cross-Reference Standards](./standards/documentation-standards.md#cross-reference-standards) +- Update navigation in README.md files + +--- + +**Document Version:** 1.0 +**Last Updated:** 2026-02-17 +**Maintainer:** DIRT Team diff --git a/bitwarden_license/bit-common/src/dirt/docs/getting-started.md b/bitwarden_license/bit-common/src/dirt/docs/getting-started.md new file mode 100644 index 00000000000..0077019fe02 --- /dev/null +++ b/bitwarden_license/bit-common/src/dirt/docs/getting-started.md @@ -0,0 +1,96 @@ +# DIRT Team - Getting Started + +**Purpose:** Navigation hub showing what documentation is available for your work + +--- + +## 🎯 DIRT Team Features + +The **DIRT team** (Data, Insights, Reporting & Tooling) owns: + +- **Access Intelligence** (formerly Risk Insights) + - Organization security reporting and password health analysis + - Location: `dirt/reports/risk-insights/` (v1 services), `bit-web/.../access-intelligence/` (UI) + - Note: `risk-insights` is the v1 codebase name for Access Intelligence + +- **Organization Integrations** + - Third-party organization integrations + - Location: `dirt/organization-integrations/` + +- **External Reports** + - Various organization reports (weak password report, member access report, etc.) + - Documentation: Coming soon + +- **Phishing Detection** + - Documentation: Coming soon + +**Note:** Access Intelligence has the most documentation as it's the first feature we're documenting comprehensively. + +--- + +## 📚 What's Available + +### Development Resources + +| Resource Type | What It Provides | Where to Find It | +| ----------------------- | ------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------- | +| **Playbooks** | Step-by-step implementation guides for common dev tasks | [Playbooks Hub](./playbooks/) | +| **Standards** | Coding conventions, patterns, and best practices | [Standards Hub](./standards/README.md) | +| **Architecture** | Feature architecture reviews and migration plans | [Access Intelligence Architecture](./access-intelligence/architecture/) | +| **Integration Guides** | How services and components work together | [Generic Guide](./integration-guide.md), [Access Intelligence](./access-intelligence/service-component-integration.md) | +| **Documentation Guide** | How docs are organized and where to find things | [Documentation Structure](./documentation-structure.md) | + +### Standards by Area + +| Area | Standard Document | +| ---------------------- | -------------------------------------------------------------------------- | +| **General Coding** | [Standards Hub](./standards/README.md) | +| **Services** | [Service Standards](./standards/service-standards.md) | +| **Domain Models** | [Model Standards](./standards/model-standards.md) | +| **Service Testing** | [Service Testing Standards](./standards/testing-standards-services.md) | +| **Angular Components** | [Angular Standards](./standards/angular-standards.md) | +| **Component Testing** | [Component Testing Standards](./standards/testing-standards-components.md) | +| **RxJS Patterns** | [RxJS Standards](./standards/rxjs-standards.md) | +| **Code Organization** | [Code Organization Standards](./standards/code-organization-standards.md) | +| **Documentation** | [Documentation Standards](./standards/documentation-standards.md) | + +### Playbooks by Task + +| Task | Playbook | +| ------------------------------------ | --------------------------------------------------------------------------------- | +| **Implement or refactor a service** | [Service Implementation Playbook](./playbooks/service-implementation-playbook.md) | +| **Migrate or create a UI component** | [Component Migration Playbook](./playbooks/component-migration-playbook.md) | +| **Create or update documentation** | [Documentation Playbook](./playbooks/documentation-playbook.md) | +| **Browse all playbooks** | [Playbooks Hub](./playbooks/) | + +--- + +## 🚀 Quick Reference by Task + +| What are you working on? | Start here | +| ---------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| **Services** (implementation, architecture, testing) | [Service Playbook](./playbooks/service-implementation-playbook.md) + [Service Standards](./standards/service-standards.md) | +| **Domain Models** (view models, query methods) | [Service Playbook](./playbooks/service-implementation-playbook.md) + [Model Standards](./standards/model-standards.md) | +| **UI Components** (Angular, migration, testing) | [Component Playbook](./playbooks/component-migration-playbook.md) + [Angular Standards](./standards/angular-standards.md) | +| **Storybook** (create or update stories) | [Component Playbook](./playbooks/component-migration-playbook.md) + [Component Testing Standards § Storybook](./standards/testing-standards-components.md#storybook-as-living-documentation) | +| **Component Tests** (Jest, OnPush, Signals) | [Component Playbook](./playbooks/component-migration-playbook.md) + [Component Testing Standards](./standards/testing-standards-components.md) | +| **Service Tests** (mocks, observables, RxJS) | [Service Playbook](./playbooks/service-implementation-playbook.md) + [Service Testing Standards](./standards/testing-standards-services.md) | +| **Documentation** (create, update, organize) | [Documentation Playbook](./playbooks/documentation-playbook.md) + [Documentation Standards](./standards/documentation-standards.md) | +| **Architecture Review** (feature planning) | [Access Intelligence Architecture](./access-intelligence/architecture/) | +| **Feature Architecture Decisions** | Document in [docs/[feature]/architecture/](./documentation-structure.md#feature-level-documentation) (decisions tracked in Confluence) | + +--- + +## 🆘 Need Help? + +**Can't find what you're looking for?** + +- **Understand how docs are organized:** See [Documentation Structure](./documentation-structure.md) +- **Browse all team documentation:** See [Team Docs README](./README.md) +- **Component-specific docs:** See [Component Docs](/bitwarden_license/bit-web/src/app/dirt/access-intelligence/docs/README.md) + +--- + +**Document Version:** 1.0 +**Last Updated:** 2026-02-17 +**Maintainer:** DIRT Team diff --git a/bitwarden_license/bit-common/src/dirt/docs/integration-guide.md b/bitwarden_license/bit-common/src/dirt/docs/integration-guide.md new file mode 100644 index 00000000000..0d7bf3db847 --- /dev/null +++ b/bitwarden_license/bit-common/src/dirt/docs/integration-guide.md @@ -0,0 +1,378 @@ +# Service ↔ Component Integration Guide + +**Purpose:** Coordination guide for features that span both platform-agnostic services (bit-common) and Angular UI components (bit-web/bit-browser) + +**Scope:** This guide applies to **any DIRT feature** requiring work in both service and component layers. For feature-specific integration patterns and detailed examples, see the feature's documentation: + +- [Access Intelligence Integration](/bitwarden_license/bit-common/src/dirt/docs/access-intelligence/service-component-integration.md) + +**Focus:** This document focuses on **coordination and handoffs** between service and component developers. For code patterns and standards, see [Standards Documentation](/bitwarden_license/bit-common/src/dirt/docs/standards/standards.md). + +--- + +## 📋 When You Need Both + +Many DIRT features require coordinated work across service AND component layers: + +| Feature Type | Service Work | Component Work | +| -------------------------- | ----------------------------- | --------------------------------- | +| **New report/data type** | Generate, persist, load data | Display data, filters, navigation | +| **New data visualization** | Aggregate/query data | Charts, cards, tables | +| **User actions** | Business logic on models | UI interactions, forms | +| **Settings/preferences** | Persist settings | Settings UI | +| **Integrations** | API communication, sync logic | Configuration UI, status display | + +--- + +## 🔄 Integration Pattern + +``` +┌─────────────────────────────────────────────────┐ +│ Component (bit-web/bit-browser) │ +│ - User interactions │ +│ - Display logic │ +│ - Converts Observables → Signals (toSignal()) │ +│ - OnPush + Signal inputs/outputs │ +├─────────────────────────────────────────────────┤ +│ Data Service (Feature-specific) │ +│ - Exposes Observable streams │ +│ - Coordinates feature data │ +│ - Delegates business logic to models │ +│ - Delegates persistence to services │ +├─────────────────────────────────────────────────┤ +│ Domain Services (bit-common) │ +│ - Business logic orchestration │ +│ - Pure transformation │ +│ - Platform-agnostic │ +├─────────────────────────────────────────────────┤ +│ View Models │ +│ - Smart models (CipherView pattern) │ +│ - Query methods: getData(), filter(), etc. │ +│ - Mutation methods: update(), delete(), etc. │ +└─────────────────────────────────────────────────┘ +``` + +**Key principle:** Services do the work, components coordinate the UI. Business logic lives in view models, not components. + +--- + +## 🔀 Service → Component Handoff + +**When:** Service implementation is complete, ready for UI integration + +### Readiness Checklist + +Before handing off to component developer, ensure: + +- [ ] **Service is complete and tested** + - [ ] Abstract defined with JSDoc + - [ ] Implementation complete + - [ ] Tests passing (`npm run test`) + - [ ] Types validated (`npm run test:types`) + +- [ ] **View models have required methods** + - [ ] Query methods for component data needs (documented) + - [ ] Mutation methods for user actions (documented) + - [ ] Methods follow naming conventions + +- [ ] **Data service exposes observables** + - [ ] Observable(s) are public and documented + - [ ] Observable emits correct view models + - [ ] Observable handles errors gracefully + +- [ ] **Component requirements documented** + - [ ] What data the component needs + - [ ] What user actions the component handles + - [ ] What the component should display + - [ ] Any performance considerations + +### Handoff Communication Template + +When handing off to component developer, provide: + +1. **What service to inject** + - Example: `FeatureDataService` + +2. **What observable(s) to use** + - Example: `data$: Observable` + - Type signature and nullability + +3. **What model methods are available** + - Query methods: `feature.getData()`, `feature.filter(criteria)` + - Mutation methods: `feature.update(data)`, `feature.delete(id)` + - Link to model documentation or JSDoc + +4. **How to integrate in component** + - Reference [Standards: Observable to Signal Conversion](/bitwarden_license/bit-common/src/dirt/docs/standards/standards.md) + - Basic pattern: inject service → convert observable to signal → use in template + +5. **Any gotchas or special considerations** + - Performance notes (large datasets, expensive operations) + - Error handling requirements + - Special states (loading, empty, error) + +### Communication Methods + +- **Slack:** Quick handoff for simple integrations +- **Jira comment:** Document handoff details on feature ticket +- **Documentation:** Update feature docs with integration examples +- **Pair session:** For complex integrations, schedule pairing + +--- + +## 🔀 Component → Service Handoff + +**When:** Component needs new data/functionality not yet available in services + +### Discovery Checklist + +Before creating a service request, identify: + +- [ ] **What's missing** + - [ ] New query method needed on view model? + - [ ] New mutation method needed on view model? + - [ ] New service needed entirely? + - [ ] New data needs to be loaded/persisted? + +- [ ] **Document the requirement clearly** + - [ ] What data the component needs (shape, type) + - [ ] What format the data should be in + - [ ] What user action triggers this need + - [ ] Performance requirements (dataset size, frequency) + +- [ ] **Assess scope** + - [ ] Is this a new method on existing model? (small change) + - [ ] Is this a new service? (medium-large change) + - [ ] Does this require API changes? (involves backend team) + +- [ ] **File appropriate ticket** + - [ ] Link to component/feature that needs it + - [ ] Link to design/mockup if applicable + - [ ] Tag service developer or tech lead + +### Handoff Communication Template + +When requesting service work, provide: + +1. **What the component needs** + - Clear description: "Component needs list of filtered items based on user criteria" + +2. **Proposed API (if you have one)** + - Example: `model.getFilteredItems(criteria): Item[]` + - This is negotiable, service developer may suggest better approach + +3. **Why (user story/context)** + - Example: "User clicks 'Show only critical' filter, UI should update to show subset" + +4. **Data format expected** + - Example: "Array of `{ id: string, name: string, isCritical: boolean }`" + - Or reference existing model type if reusing + +5. **Performance/scale considerations** + - Example: "Could be 1000+ items for large organizations" + - Helps service developer optimize + +6. **Timeline/priority** + - Is this blocking component work? + - Can component proceed with stub/mock for now? + +### Communication Methods + +- **Jira ticket:** For non-trivial work requiring tracking +- **Slack:** For quick questions or small additions +- **Planning session:** For large features requiring design discussion +- **ADR:** If architectural decision needed + +--- + +## 🤝 Collaboration Patterns + +### Pattern 1: Parallel Development + +**When to use:** Service and component work can be developed simultaneously + +**How:** + +1. Service developer creates interface/abstract first +2. Component developer uses interface with mock data +3. Both develop in parallel +4. Integration happens at the end + +**Benefits:** Faster delivery, clear contracts + +### Pattern 2: Sequential Development (Service First) + +**When to use:** Component needs complete service implementation + +**How:** + +1. Service developer implements fully +2. Service developer documents integration +3. Component developer integrates +4. Component developer provides feedback + +**Benefits:** Fewer integration issues, clearer requirements + +### Pattern 3: Sequential Development (Component First) + +**When to use:** UI/UX needs to be proven before service investment + +**How:** + +1. Component developer builds with mock data +2. Component developer documents data needs +3. Service developer implements to match needs +4. Integration and refinement + +**Benefits:** User-driven design, avoids unused service work + +### Pattern 4: Paired Development + +**When to use:** Complex integration, unclear requirements, new patterns + +**How:** + +1. Service and component developer pair on design +2. Develop together or in short iterations +3. Continuous feedback and adjustment + +**Benefits:** Fastest problem solving, shared understanding + +--- + +## 🧪 Testing Integration Points + +### Service Layer Testing + +**Service developers should test:** + +- Services return correct view models +- Observables emit expected data +- Error handling works correctly +- Performance is acceptable for expected dataset sizes + +**Reference:** [Service Implementation Playbook - Testing](/bitwarden_license/bit-common/src/dirt/docs/playbooks/service-implementation-playbook.md) + +### Component Layer Testing + +**Component developers should test:** + +- Services are correctly injected +- Observables are correctly converted to signals +- View model methods are called appropriately +- Data is displayed correctly +- User interactions trigger correct model methods + +**Reference:** [Component Migration Playbook - Testing](/bitwarden_license/bit-common/src/dirt/docs/playbooks/component-migration-playbook.md) + +### Integration Testing + +**Both should coordinate on:** + +- Full user flows work end-to-end +- Data flows correctly from service → component +- UI updates when data changes +- Error states are handled gracefully + +--- + +## 🚨 Common Integration Pitfalls + +### 1. Component Bypasses Data Service + +**Problem:** Component directly calls API services or persistence layers + +**Why it's bad:** Breaks abstraction, duplicates logic, harder to test + +**Solution:** Always go through feature's data service layer + +**Reference:** [Standards: Service Layer Pattern](/bitwarden_license/bit-common/src/dirt/docs/standards/standards.md) + +### 2. Service Returns Plain Objects + +**Problem:** Service returns `{ ... }` instead of view model instances + +**Why it's bad:** Loses model methods, breaks encapsulation, business logic leaks to components + +**Solution:** Always return view model instances with query/mutation methods + +**Reference:** [Standards: View Models](/bitwarden_license/bit-common/src/dirt/docs/standards/standards.md) + +### 3. Business Logic in Components + +**Problem:** Component implements filtering, calculations, state changes + +**Why it's bad:** Logic not reusable, harder to test, violates separation of concerns + +**Solution:** Business logic belongs in view models or domain services + +**Reference:** [Standards: Component Responsibilities](/bitwarden_license/bit-common/src/dirt/docs/standards/standards.md) + +### 4. Manual Observable Subscriptions + +**Problem:** Component uses `.subscribe()` instead of `toSignal()` + +**Why it's bad:** Memory leaks, manual cleanup needed, doesn't leverage Angular signals + +**Solution:** Use `toSignal()` for automatic cleanup and signal integration + +**Reference:** [Standards: Observable to Signal Conversion](/bitwarden_license/bit-common/src/dirt/docs/standards/standards.md) + +### 5. Unclear Handoff + +**Problem:** Service developer finishes work but doesn't communicate to component developer + +**Why it's bad:** Delays integration, component developer doesn't know work is ready + +**Solution:** Use handoff communication templates above, update Jira tickets, notify in Slack + +--- + +## 📞 Who to Contact + +### Service Questions + +- Check: [Service Implementation Playbook](/bitwarden_license/bit-common/src/dirt/docs/playbooks/service-implementation-playbook.md) +- Check: [Standards](/bitwarden_license/bit-common/src/dirt/docs/standards/standards.md) +- Ask: DIRT team service developers + +### Component Questions + +- Check: [Component Migration Playbook](/bitwarden_license/bit-common/src/dirt/docs/playbooks/component-migration-playbook.md) +- Check: [Standards](/bitwarden_license/bit-common/src/dirt/docs/standards/standards.md) +- Ask: DIRT team component developers + +### Architecture Questions + +- Check: [Architecture Docs](/bitwarden_license/bit-common/src/dirt/docs/access-intelligence/architecture/) +- Check: [Getting Started](/bitwarden_license/bit-common/src/dirt/docs/getting-started.md) +- Ask: DIRT team tech lead + +### Coordination/Process Questions + +- Ask: DIRT team lead or scrum master + +--- + +## 📚 Related Documentation + +### General Guides + +- [Getting Started](/bitwarden_license/bit-common/src/dirt/docs/getting-started.md) +- [Standards](/bitwarden_license/bit-common/src/dirt/docs/standards/standards.md) +- [Documentation Structure](/bitwarden_license/bit-common/src/dirt/docs/documentation-structure.md) + +### Implementation Playbooks + +- [Service Implementation Playbook](/bitwarden_license/bit-common/src/dirt/docs/playbooks/service-implementation-playbook.md) +- [Component Migration Playbook](/bitwarden_license/bit-common/src/dirt/docs/playbooks/component-migration-playbook.md) + +### Feature-Specific Integration Guides + +- [Access Intelligence Integration](/bitwarden_license/bit-common/src/dirt/docs/access-intelligence/service-component-integration.md) + +--- + +**Document Version:** 1.0 +**Last Updated:** 2026-02-17 +**Maintainer:** DIRT Team diff --git a/bitwarden_license/bit-common/src/dirt/organization-integrations/models/integration-configuration-config/configuration-template/datadog-template.ts b/bitwarden_license/bit-common/src/dirt/organization-integrations/models/integration-configuration-config/configuration-template/datadog-template.ts index b5816ba34a2..5f642dd6503 100644 --- a/bitwarden_license/bit-common/src/dirt/organization-integrations/models/integration-configuration-config/configuration-template/datadog-template.ts +++ b/bitwarden_license/bit-common/src/dirt/organization-integrations/models/integration-configuration-config/configuration-template/datadog-template.ts @@ -14,19 +14,27 @@ export class DatadogTemplate implements OrgIntegrationTemplate { ddsource: "bitwarden", service: "event-logs", event: { - service: "payments", object: "event", - type: "#Type#", + type: "#TypeId#", + typeName: "#Type#", + memberId: "#UserId#", + organizationId: "#OrganizationId#", + providerId: "#ProviderId#", itemId: "#CipherId#", collectionId: "#CollectionId#", groupId: "#GroupId#", policyId: "#PolicyId#", - memberId: "#UserId#", + organizationUserId: "#OrganizationUserId#", + providerUserId: "#ProviderUserId#", + providerOrganizationId: "#ProviderOrganizationId#", actingUserId: "#ActingUserId#", installationId: "#InstallationId#", date: "#DateIso8601#", - device: "#DeviceType#", + deviceType: "#DeviceType#", + device: "#DeviceTypeId#", ipAddress: "#IpAddress#", + systemUser: "#SystemUser#", + domainName: "#DomainName#", secretId: "#SecretId#", projectId: "#ProjectId#", serviceAccountId: "#ServiceAccountId#", diff --git a/bitwarden_license/bit-common/src/dirt/organization-integrations/models/integration-configuration-config/configuration-template/hec-template.ts b/bitwarden_license/bit-common/src/dirt/organization-integrations/models/integration-configuration-config/configuration-template/hec-template.ts index 3c0cf3b9b35..2da8624c66f 100644 --- a/bitwarden_license/bit-common/src/dirt/organization-integrations/models/integration-configuration-config/configuration-template/hec-template.ts +++ b/bitwarden_license/bit-common/src/dirt/organization-integrations/models/integration-configuration-config/configuration-template/hec-template.ts @@ -17,27 +17,29 @@ export class HecTemplate implements OrgIntegrationTemplate { service: "event-logs", event: { object: "event", - type: "#Type#", + type: "#TypeId#", + typeName: "#Type#", + memberId: "#UserId#", + organizationId: "#OrganizationId#", + providerId: "#ProviderId#", itemId: "#CipherId#", collectionId: "#CollectionId#", groupId: "#GroupId#", policyId: "#PolicyId#", - memberId: "#UserId#", + organizationUserId: "#OrganizationUserId#", + providerUserId: "#ProviderUserId#", + providerOrganizationId: "#ProviderOrganizationId#", actingUserId: "#ActingUserId#", installationId: "#InstallationId#", date: "#DateIso8601#", - device: "#DeviceType#", + deviceType: "#DeviceType#", + device: "#DeviceTypeId#", ipAddress: "#IpAddress#", + systemUser: "#SystemUser#", + domainName: "#DomainName#", secretId: "#SecretId#", projectId: "#ProjectId#", serviceAccountId: "#ServiceAccountId#", - actingUserName: "#ActingUserName#", - actingUserEmail: "#ActingUserEmail#", - actingUserType: "#ActingUserType#", - userName: "#UserName#", - userEmail: "#UserEmail#", - userType: "#UserType#", - groupName: "#GroupName#", }, }; diff --git a/bitwarden_license/bit-web/src/app/admin-console/providers/manage/events.component.html b/bitwarden_license/bit-web/src/app/admin-console/providers/manage/events.component.html index 070505a53b2..c79b39a6feb 100644 --- a/bitwarden_license/bit-web/src/app/admin-console/providers/manage/events.component.html +++ b/bitwarden_license/bit-web/src/app/admin-console/providers/manage/events.component.html @@ -50,56 +50,64 @@
    - - - {{ "loading" | i18n }} - - -

    {{ "noEventsInList" | i18n }}

    - - - - {{ "timestamp" | i18n }} - {{ "device" | i18n }} - {{ "user" | i18n }} - {{ "event" | i18n }} - - - - - {{ e.date | date: "medium" }} - - - {{ e.appName }}, {{ e.ip }} - - - {{ e.userName }} - - - - - - -
    + > + {{ "loading" | i18n }} + +} +@if (loaded()) { + + @if (!events() || !events().length) { +

    {{ "noEventsInList" | i18n }}

    + } + + @if (events() && events().length) { + + + + {{ "timestamp" | i18n }} + {{ "device" | i18n }} + {{ "user" | i18n }} + {{ "event" | i18n }} + + + + @for (e of events(); track i; let i = $index) { + + {{ e.date | date: "medium" }} + + + {{ e.appName }}, {{ e.ip }} + + + {{ e.userName }} + + + + } + + + } + @if (continuationToken) { + + } +
    +} diff --git a/bitwarden_license/bit-web/src/app/admin-console/providers/manage/events.component.ts b/bitwarden_license/bit-web/src/app/admin-console/providers/manage/events.component.ts index 3d00d897175..fe14e56bbaa 100644 --- a/bitwarden_license/bit-web/src/app/admin-console/providers/manage/events.component.ts +++ b/bitwarden_license/bit-web/src/app/admin-console/providers/manage/events.component.ts @@ -94,7 +94,7 @@ export class EventsComponent extends BaseEventsComponent implements OnInit { this.providerUsersUserIdMap.set(u.userId, { name: name, email: u.email }); }); await this.refreshEvents(); - this.loaded = true; + this.loaded.set(true); } protected requestEvents(startDate: string, endDate: string, continuationToken: string) { diff --git a/bitwarden_license/bit-web/src/app/dirt/access-intelligence/activity/application-review-dialog/new-applications-dialog.component.ts b/bitwarden_license/bit-web/src/app/dirt/access-intelligence/activity/application-review-dialog/new-applications-dialog.component.ts index 5b9cea436a0..13018ba6884 100644 --- a/bitwarden_license/bit-web/src/app/dirt/access-intelligence/activity/application-review-dialog/new-applications-dialog.component.ts +++ b/bitwarden_license/bit-web/src/app/dirt/access-intelligence/activity/application-review-dialog/new-applications-dialog.component.ts @@ -9,8 +9,8 @@ import { Signal, signal, } from "@angular/core"; -import { takeUntilDestroyed, toSignal } from "@angular/core/rxjs-interop"; -import { catchError, EMPTY, from, switchMap, take } from "rxjs"; +import { toSignal } from "@angular/core/rxjs-interop"; +import { firstValueFrom } from "rxjs"; import { ApplicationHealthReportDetail, @@ -238,6 +238,12 @@ export class NewApplicationsDialogComponent { // Checks if there are selected applications and proceeds to assign tasks async handleMarkAsCritical() { + if (this.markingAsCritical()) { + return; // Prevent double-click + } + + this.markingAsCritical.set(true); + if (this.selectedApplications().size === 0) { const confirmed = await this.dialogService.openSimpleDialog({ title: { key: "confirmNoSelectedCriticalApplicationsTitle" }, @@ -246,25 +252,11 @@ export class NewApplicationsDialogComponent { }); if (!confirmed) { + this.markingAsCritical.set(false); return; } } - // Skip the assign tasks view if there are no new unassigned at-risk cipher IDs - if (this.newUnassignedAtRiskCipherIds().length === 0) { - this.handleAssignTasks(); - } else { - this.currentView.set(DialogView.AssignTasks); - } - } - - // Saves the application review and assigns tasks for unassigned at-risk ciphers - protected handleAssignTasks() { - if (this.saving()) { - return; // Prevent double-click - } - this.saving.set(true); - const reviewedDate = new Date(); const updatedApplications = this.dialogParams.newApplications.map((app) => { const isCritical = this.selectedApplications().has(app.applicationName); @@ -276,56 +268,79 @@ export class NewApplicationsDialogComponent { }); // Save the application review dates and critical markings - this.dataService - .saveApplicationReviewStatus(updatedApplications) - .pipe( - takeUntilDestroyed(this.destroyRef), // Satisfy eslint rule - take(1), - switchMap(() => { - // Assign password change tasks for unassigned at-risk ciphers for critical applications - return from( - this.securityTasksService.requestPasswordChangeForCriticalApplications( - this.dialogParams.organizationId, - this.newUnassignedAtRiskCipherIds(), - ), - ); - }), - catchError((error: unknown) => { - if (error instanceof ErrorResponse && error.statusCode === 404) { - this.toastService.showToast({ - message: this.i18nService.t("mustBeOrganizationOwnerAdmin"), - variant: "error", - title: this.i18nService.t("error"), - }); + try { + await firstValueFrom(this.dataService.saveApplicationReviewStatus(updatedApplications)); - this.saving.set(false); - return EMPTY; - } - - this.logService.error( - "[NewApplicationsDialog] Failed to save application review or assign tasks", - error, - ); - this.saving.set(false); - this.toastService.showToast({ - variant: "error", - title: this.i18nService.t("errorSavingReviewStatus"), - message: this.i18nService.t("pleaseTryAgain"), - }); - - this.saving.set(false); - return EMPTY; - }), - ) - .subscribe(() => { - this.toastService.showToast({ - variant: "success", - title: this.i18nService.t("applicationReviewSaved"), - message: this.i18nService.t("newApplicationsReviewed"), - }); - this.saving.set(false); - this.handleAssigningCompleted(); + this.toastService.showToast({ + variant: "success", + title: this.i18nService.t("applicationReviewSaved"), + message: this.i18nService.t("newApplicationsReviewed"), }); + + // If there are no unassigned at-risk ciphers, we can complete immediately. Otherwise, navigate to the assign tasks view. + if (this.newUnassignedAtRiskCipherIds().length === 0) { + this.handleAssigningCompleted(); + } else { + this.currentView.set(DialogView.AssignTasks); + } + } catch (error: unknown) { + this.logService.error( + "[NewApplicationsDialog] Failed to save application review status", + error, + ); + + this.toastService.showToast({ + variant: "error", + title: this.i18nService.t("errorSavingReviewStatus"), + message: this.i18nService.t("pleaseTryAgain"), + }); + } finally { + this.markingAsCritical.set(false); + } + } + + // Saves the application review and assigns tasks for unassigned at-risk ciphers + protected async handleAssignTasks() { + if (this.saving()) { + return; // Prevent double-click + } + this.saving.set(true); + + try { + await this.securityTasksService.requestPasswordChangeForCriticalApplications( + this.dialogParams.organizationId, + this.newUnassignedAtRiskCipherIds(), + ); + + this.toastService.showToast({ + variant: "success", + title: this.i18nService.t("success"), + message: this.i18nService.t("notifiedMembers"), + }); + + // close the dialog + this.handleAssigningCompleted(); + } catch (error: unknown) { + if (error instanceof ErrorResponse && error.statusCode === 404) { + this.toastService.showToast({ + message: this.i18nService.t("mustBeOrganizationOwnerAdmin"), + variant: "error", + title: this.i18nService.t("error"), + }); + + return; + } + + this.logService.error("[NewApplicationsDialog] Failed to assign tasks", error); + + this.toastService.showToast({ + message: this.i18nService.t("unexpectedError"), + variant: "error", + title: this.i18nService.t("error"), + }); + } finally { + this.saving.set(false); + } } /** diff --git a/bitwarden_license/bit-web/src/app/dirt/access-intelligence/all-applications/applications.component.html b/bitwarden_license/bit-web/src/app/dirt/access-intelligence/all-applications/applications.component.html index 27864fa2f87..ec73c4f47e6 100644 --- a/bitwarden_license/bit-web/src/app/dirt/access-intelligence/all-applications/applications.component.html +++ b/bitwarden_license/bit-web/src/app/dirt/access-intelligence/all-applications/applications.component.html @@ -22,7 +22,7 @@
    - @if (selectedUrls().size > 0) { + @if (visibleSelectedApps().size > 0) { @if (allSelectedAppsAreCritical()) { } @else { } } @@ -79,14 +79,15 @@ [dataSource]="dataSource" [selectedUrls]="selectedUrls()" [openApplication]="drawerDetails.invokerId || ''" - [checkboxChange]="onCheckboxChange" [showAppAtRiskMembers]="showAppAtRiskMembers" + (checkboxChange)="onCheckboxChange($event)" + (selectAllChange)="onSelectAllChange($event)" class="tw-mb-10" > - @if (emptyTableExplanation()) { + @if (this.dataSource.filteredData?.length === 0) {
    - {{ emptyTableExplanation() }} + {{ "noApplicationsMatchTheseFilters" | i18n }}
    }
    diff --git a/bitwarden_license/bit-web/src/app/dirt/access-intelligence/all-applications/applications.component.spec.ts b/bitwarden_license/bit-web/src/app/dirt/access-intelligence/all-applications/applications.component.spec.ts index b4cbbc5c436..b79f5160bf7 100644 --- a/bitwarden_license/bit-web/src/app/dirt/access-intelligence/all-applications/applications.component.spec.ts +++ b/bitwarden_license/bit-web/src/app/dirt/access-intelligence/all-applications/applications.component.spec.ts @@ -1,8 +1,9 @@ +import { Signal, WritableSignal } from "@angular/core"; import { ComponentFixture, TestBed } from "@angular/core/testing"; import { ReactiveFormsModule } from "@angular/forms"; -import { ActivatedRoute } from "@angular/router"; +import { ActivatedRoute, convertToParamMap } from "@angular/router"; import { mock, MockProxy } from "jest-mock-extended"; -import { BehaviorSubject } from "rxjs"; +import { BehaviorSubject, of } from "rxjs"; import { DrawerDetails, @@ -11,6 +12,7 @@ import { ReportStatus, RiskInsightsDataService, } from "@bitwarden/bit-common/dirt/reports/risk-insights"; +import { createNewSummaryData } from "@bitwarden/bit-common/dirt/reports/risk-insights/helpers"; import { RiskInsightsEnrichedData } from "@bitwarden/bit-common/dirt/reports/risk-insights/models/report-data-service.types"; import { FileDownloadService } from "@bitwarden/common/platform/abstractions/file-download/file-download.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; @@ -23,9 +25,18 @@ import { AccessIntelligenceSecurityTasksService } from "../shared/security-tasks import { ApplicationsComponent } from "./applications.component"; +// Mock ResizeObserver +global.ResizeObserver = class ResizeObserver { + observe() {} + unobserve() {} + disconnect() {} +}; + // Helper type to access protected members in tests type ComponentWithProtectedMembers = ApplicationsComponent & { dataSource: TableDataSource; + selectedUrls: WritableSignal>; + filteredTableData: Signal; }; describe("ApplicationsComponent", () => { @@ -83,7 +94,10 @@ describe("ApplicationsComponent", () => { { provide: RiskInsightsDataService, useValue: mockDataService }, { provide: ActivatedRoute, - useValue: { snapshot: { paramMap: { get: (): string | null => null } } }, + useValue: { + paramMap: of(convertToParamMap({})), + snapshot: { paramMap: convertToParamMap({}) }, + }, }, { provide: AccessIntelligenceSecurityTasksService, useValue: mockSecurityTasksService }, ], @@ -91,6 +105,7 @@ describe("ApplicationsComponent", () => { fixture = TestBed.createComponent(ApplicationsComponent); component = fixture.componentInstance; + fixture.detectChanges(); }); afterEach(() => { @@ -247,4 +262,185 @@ describe("ApplicationsComponent", () => { expect(capturedBlobData).not.toContain("Slack"); }); }); + + describe("checkbox selection", () => { + const mockApplicationData: ApplicationTableDataSource[] = [ + { + applicationName: "GitHub", + passwordCount: 10, + atRiskPasswordCount: 3, + memberCount: 5, + atRiskMemberCount: 2, + isMarkedAsCritical: true, + atRiskCipherIds: ["cipher1" as CipherId], + memberDetails: [] as MemberDetails[], + atRiskMemberDetails: [] as MemberDetails[], + cipherIds: ["cipher1" as CipherId], + iconCipher: undefined, + }, + { + applicationName: "Slack", + passwordCount: 8, + atRiskPasswordCount: 1, + memberCount: 4, + atRiskMemberCount: 1, + isMarkedAsCritical: false, + atRiskCipherIds: ["cipher2" as CipherId], + memberDetails: [] as MemberDetails[], + atRiskMemberDetails: [] as MemberDetails[], + cipherIds: ["cipher2" as CipherId], + iconCipher: undefined, + }, + { + applicationName: "Jira", + passwordCount: 12, + atRiskPasswordCount: 4, + memberCount: 6, + atRiskMemberCount: 3, + isMarkedAsCritical: false, + atRiskCipherIds: ["cipher3" as CipherId], + memberDetails: [] as MemberDetails[], + atRiskMemberDetails: [] as MemberDetails[], + cipherIds: ["cipher3" as CipherId], + iconCipher: undefined, + }, + ]; + + beforeEach(() => { + // Emit mock data through the data service observable to populate the table + enrichedReportData$.next({ + reportData: mockApplicationData, + summaryData: createNewSummaryData(), + applicationData: [], + creationDate: new Date(), + }); + }); + + describe("onCheckboxChange", () => { + it("should add application to selectedUrls when checked is true", () => { + // act + component.onCheckboxChange({ applicationName: "GitHub", checked: true }); + + // assert + const selectedUrls = (component as ComponentWithProtectedMembers).selectedUrls(); + expect(selectedUrls.has("GitHub")).toBe(true); + expect(selectedUrls.size).toBe(1); + }); + + it("should remove application from selectedUrls when checked is false", () => { + // arrange + (component as ComponentWithProtectedMembers).selectedUrls.set(new Set(["GitHub", "Slack"])); + + // act + component.onCheckboxChange({ applicationName: "GitHub", checked: false }); + + // assert + const selectedUrls = (component as ComponentWithProtectedMembers).selectedUrls(); + expect(selectedUrls.has("GitHub")).toBe(false); + expect(selectedUrls.has("Slack")).toBe(true); + expect(selectedUrls.size).toBe(1); + }); + + it("should handle multiple applications being selected", () => { + // act + component.onCheckboxChange({ applicationName: "GitHub", checked: true }); + component.onCheckboxChange({ applicationName: "Slack", checked: true }); + component.onCheckboxChange({ applicationName: "Jira", checked: true }); + + // assert + const selectedUrls = (component as ComponentWithProtectedMembers).selectedUrls(); + expect(selectedUrls.has("GitHub")).toBe(true); + expect(selectedUrls.has("Slack")).toBe(true); + expect(selectedUrls.has("Jira")).toBe(true); + expect(selectedUrls.size).toBe(3); + }); + }); + + describe("onSelectAllChange", () => { + it("should add all visible applications to selectedUrls when checked is true", () => { + // act + component.onSelectAllChange(true); + + // assert + const selectedUrls = (component as ComponentWithProtectedMembers).selectedUrls(); + expect(selectedUrls.has("GitHub")).toBe(true); + expect(selectedUrls.has("Slack")).toBe(true); + expect(selectedUrls.has("Jira")).toBe(true); + expect(selectedUrls.size).toBe(3); + }); + + it("should remove all applications from selectedUrls when checked is false", () => { + // arrange + (component as ComponentWithProtectedMembers).selectedUrls.set(new Set(["GitHub", "Slack"])); + + // act + component.onSelectAllChange(false); + + // assert + const selectedUrls = (component as ComponentWithProtectedMembers).selectedUrls(); + expect(selectedUrls.size).toBe(0); + }); + + it("should only add visible filtered applications when filter is applied", () => { + // arrange - apply filter to only show critical apps + (component as ComponentWithProtectedMembers).dataSource.filter = ( + app: ApplicationTableDataSource, + ) => app.isMarkedAsCritical; + fixture.detectChanges(); + + // act + component.onSelectAllChange(true); + + // assert - only GitHub is critical + const selectedUrls = (component as ComponentWithProtectedMembers).selectedUrls(); + expect(selectedUrls.has("GitHub")).toBe(true); + expect(selectedUrls.has("Slack")).toBe(false); + expect(selectedUrls.has("Jira")).toBe(false); + expect(selectedUrls.size).toBe(1); + }); + + it("should only remove visible filtered applications when unchecking with filter applied", () => { + // arrange - select all apps first, then apply filter to only show non-critical apps + (component as ComponentWithProtectedMembers).selectedUrls.set( + new Set(["GitHub", "Slack", "Jira"]), + ); + + (component as ComponentWithProtectedMembers).dataSource.filter = ( + app: ApplicationTableDataSource, + ) => !app.isMarkedAsCritical; + fixture.detectChanges(); + + // act - uncheck with filter applied + component.onSelectAllChange(false); + + // assert - GitHub (critical) should still be selected + const selectedUrls = (component as ComponentWithProtectedMembers).selectedUrls(); + expect(selectedUrls.has("GitHub")).toBe(true); + expect(selectedUrls.has("Slack")).toBe(false); + expect(selectedUrls.has("Jira")).toBe(false); + expect(selectedUrls.size).toBe(1); + }); + + it("should preserve existing selections when checking select all with filter", () => { + // arrange - select a non-visible app + (component as ComponentWithProtectedMembers).selectedUrls.set(new Set(["GitHub"])); + + // apply filter to hide GitHub + (component as ComponentWithProtectedMembers).dataSource.filter = ( + app: ApplicationTableDataSource, + ) => !app.isMarkedAsCritical; + fixture.detectChanges(); + + // act - select all visible (non-critical apps) + component.onSelectAllChange(true); + + // assert - GitHub should still be selected + visible apps + const selectedUrls = (component as ComponentWithProtectedMembers).selectedUrls(); + expect(selectedUrls.has("GitHub")).toBe(true); + expect(selectedUrls.has("Slack")).toBe(true); + expect(selectedUrls.has("Jira")).toBe(true); + expect(selectedUrls.size).toBe(3); + }); + }); + }); }); diff --git a/bitwarden_license/bit-web/src/app/dirt/access-intelligence/all-applications/applications.component.ts b/bitwarden_license/bit-web/src/app/dirt/access-intelligence/all-applications/applications.component.ts index 0020106ba7d..659e099641c 100644 --- a/bitwarden_license/bit-web/src/app/dirt/access-intelligence/all-applications/applications.component.ts +++ b/bitwarden_license/bit-web/src/app/dirt/access-intelligence/all-applications/applications.component.ts @@ -89,6 +89,9 @@ export class ApplicationsComponent implements OnInit { // Standard properties protected readonly dataSource = new TableDataSource(); protected readonly searchControl = new FormControl("", { nonNullable: true }); + protected readonly filteredTableData = toSignal(this.dataSource.connect(), { + initialValue: [], + }); // Template driven properties protected readonly selectedUrls = signal(new Set()); @@ -117,15 +120,36 @@ export class ApplicationsComponent implements OnInit { icon: " ", }, ]); - protected readonly emptyTableExplanation = signal(""); + + // Computed property that returns only selected applications that are currently visible in filtered data + readonly visibleSelectedApps = computed(() => { + const filteredData = this.filteredTableData(); + const selected = this.selectedUrls(); + + if (!filteredData || selected.size === 0) { + return new Set(); + } + + const visibleSelected = new Set(); + filteredData.forEach((row) => { + if (selected.has(row.applicationName)) { + visibleSelected.add(row.applicationName); + } + }); + + return visibleSelected; + }); readonly allSelectedAppsAreCritical = computed(() => { - if (!this.dataSource.filteredData || this.selectedUrls().size == 0) { + const visibleSelected = this.visibleSelectedApps(); + const filteredData = this.filteredTableData(); + + if (!filteredData || visibleSelected.size === 0) { return false; } - return this.dataSource.filteredData - .filter((row) => this.selectedUrls().has(row.applicationName)) + return filteredData + .filter((row) => visibleSelected.has(row.applicationName)) .every((row) => row.isMarkedAsCritical); }); @@ -174,6 +198,9 @@ export class ApplicationsComponent implements OnInit { })); this.dataSource.data = tableDataWithIcon; this.totalApplicationsCount.set(report.reportData.length); + this.criticalApplicationsCount.set( + report.reportData.filter((app) => app.isMarkedAsCritical).length, + ); } else { this.dataSource.data = []; } @@ -183,16 +210,6 @@ export class ApplicationsComponent implements OnInit { }, }); - this.dataService.criticalReportResults$.pipe(takeUntilDestroyed(this.destroyRef)).subscribe({ - next: (criticalReport) => { - if (criticalReport != null) { - this.criticalApplicationsCount.set(criticalReport.reportData.length); - } else { - this.criticalApplicationsCount.set(0); - } - }, - }); - combineLatest([ this.searchControl.valueChanges.pipe(startWith("")), this.selectedFilterObservable, @@ -210,21 +227,6 @@ export class ApplicationsComponent implements OnInit { this.dataSource.filter = (app) => filterFunction(app) && app.applicationName.toLowerCase().includes(searchText.toLowerCase()); - - // filter selectedUrls down to only applications showing with active filters - const filteredUrls = new Set(); - this.dataSource.filteredData?.forEach((row) => { - if (this.selectedUrls().has(row.applicationName)) { - filteredUrls.add(row.applicationName); - } - }); - this.selectedUrls.set(filteredUrls); - - if (this.dataSource?.filteredData?.length === 0) { - this.emptyTableExplanation.set(this.i18nService.t("noApplicationsMatchTheseFilters")); - } else { - this.emptyTableExplanation.set(""); - } }); } @@ -232,15 +234,16 @@ export class ApplicationsComponent implements OnInit { this.selectedFilter.set(value); } - markAppsAsCritical = async () => { + async markAppsAsCritical() { this.updatingCriticalApps.set(true); - const count = this.selectedUrls().size; + const visibleSelected = this.visibleSelectedApps(); + const count = visibleSelected.size; this.dataService - .saveCriticalApplications(Array.from(this.selectedUrls())) + .saveCriticalApplications(Array.from(visibleSelected)) .pipe(takeUntilDestroyed(this.destroyRef)) .subscribe({ - next: () => { + next: (response) => { this.toastService.showToast({ variant: "success", title: "", @@ -248,6 +251,9 @@ export class ApplicationsComponent implements OnInit { }); this.selectedUrls.set(new Set()); this.updatingCriticalApps.set(false); + this.criticalApplicationsCount.set( + response?.data?.summaryData?.totalCriticalApplicationCount ?? 0, + ); }, error: () => { this.toastService.showToast({ @@ -257,17 +263,17 @@ export class ApplicationsComponent implements OnInit { }); }, }); - }; + } - unmarkAppsAsCritical = async () => { + async unmarkAppsAsCritical() { this.updatingCriticalApps.set(true); - const appsToUnmark = this.selectedUrls(); + const appsToUnmark = this.visibleSelectedApps(); this.dataService .removeCriticalApplications(appsToUnmark) .pipe(takeUntilDestroyed(this.destroyRef)) .subscribe({ - next: () => { + next: (response) => { this.toastService.showToast({ message: this.i18nService.t( "numApplicationsUnmarkedCriticalSuccess", @@ -277,6 +283,9 @@ export class ApplicationsComponent implements OnInit { }); this.selectedUrls.set(new Set()); this.updatingCriticalApps.set(false); + this.criticalApplicationsCount.set( + response?.data?.summaryData?.totalCriticalApplicationCount ?? 0, + ); }, error: () => { this.toastService.showToast({ @@ -286,7 +295,7 @@ export class ApplicationsComponent implements OnInit { }); }, }); - }; + } async requestPasswordChange() { const orgId = this.organizationId(); @@ -318,24 +327,38 @@ export class ApplicationsComponent implements OnInit { } } - showAppAtRiskMembers = async (applicationName: string) => { + async showAppAtRiskMembers(applicationName: string) { await this.dataService.setDrawerForAppAtRiskMembers(applicationName); - }; + } - onCheckboxChange = (applicationName: string, event: Event) => { - const isChecked = (event.target as HTMLInputElement).checked; + onCheckboxChange({ applicationName, checked }: { applicationName: string; checked: boolean }) { this.selectedUrls.update((selectedUrls) => { const nextSelected = new Set(selectedUrls); - if (isChecked) { + if (checked) { nextSelected.add(applicationName); } else { nextSelected.delete(applicationName); } return nextSelected; }); - }; + } - downloadApplicationsCSV = () => { + onSelectAllChange(checked: boolean) { + const filteredData = this.filteredTableData(); + if (!filteredData) { + return; + } + + this.selectedUrls.update((selectedUrls) => { + const nextSelected = new Set(selectedUrls); + filteredData.forEach((row) => + checked ? nextSelected.add(row.applicationName) : nextSelected.delete(row.applicationName), + ); + return nextSelected; + }); + } + + downloadApplicationsCSV() { try { const data = this.dataSource.filteredData; if (!data || data.length === 0) { @@ -368,5 +391,5 @@ export class ApplicationsComponent implements OnInit { } catch (error) { this.logService.error("Failed to download applications CSV", error); } - }; + } } diff --git a/bitwarden_license/bit-web/src/app/dirt/access-intelligence/shared/app-table-row-scrollable-m11.component.html b/bitwarden_license/bit-web/src/app/dirt/access-intelligence/shared/app-table-row-scrollable-m11.component.html index 05dec048328..ddbc977fc13 100644 --- a/bitwarden_license/bit-web/src/app/dirt/access-intelligence/shared/app-table-row-scrollable-m11.component.html +++ b/bitwarden_license/bit-web/src/app/dirt/access-intelligence/shared/app-table-row-scrollable-m11.component.html @@ -33,7 +33,7 @@ bitCheckbox type="checkbox" [checked]="selectedUrls().has(row.applicationName)" - (change)="checkboxChange()(row.applicationName, $event)" + (change)="checkboxChanged($event.target, row.applicationName)" /> { selectAllCheckboxEl = fixture.debugElement.query(By.css('[data-testid="selectAll"]')); }); - it("should check all rows in table when checked", () => { + it("should emit selectAllChange event with true when checked", () => { // arrange const selectedUrls = new Set(); const dataSource = new TableDataSource(); @@ -121,18 +121,19 @@ describe("AppTableRowScrollableM11Component", () => { fixture.componentRef.setInput("dataSource", dataSource); fixture.detectChanges(); + const selectAllChangeSpy = jest.fn(); + fixture.componentInstance.selectAllChange.subscribe(selectAllChangeSpy); + // act selectAllCheckboxEl.nativeElement.click(); fixture.detectChanges(); // assert - expect(selectedUrls.has("google.com")).toBe(true); - expect(selectedUrls.has("facebook.com")).toBe(true); - expect(selectedUrls.has("twitter.com")).toBe(true); - expect(selectedUrls.size).toBe(3); + expect(selectAllChangeSpy).toHaveBeenCalledWith(true); + expect(selectAllChangeSpy).toHaveBeenCalledTimes(1); }); - it("should uncheck all rows in table when unchecked", () => { + it("should emit selectAllChange event with false when unchecked", () => { // arrange const selectedUrls = new Set(["google.com", "facebook.com", "twitter.com"]); const dataSource = new TableDataSource(); @@ -142,12 +143,16 @@ describe("AppTableRowScrollableM11Component", () => { fixture.componentRef.setInput("dataSource", dataSource); fixture.detectChanges(); + const selectAllChangeSpy = jest.fn(); + fixture.componentInstance.selectAllChange.subscribe(selectAllChangeSpy); + // act selectAllCheckboxEl.nativeElement.click(); fixture.detectChanges(); // assert - expect(selectedUrls.size).toBe(0); + expect(selectAllChangeSpy).toHaveBeenCalledWith(false); + expect(selectAllChangeSpy).toHaveBeenCalledTimes(1); }); it("should become checked when all rows in table are checked", () => { @@ -178,4 +183,59 @@ describe("AppTableRowScrollableM11Component", () => { expect(selectAllCheckboxEl.nativeElement.checked).toBe(false); }); }); + + describe("individual row checkbox", () => { + it("should emit checkboxChange event with correct parameters when checkboxChanged is called", () => { + // arrange + const checkboxChangeSpy = jest.fn(); + fixture.componentInstance.checkboxChange.subscribe(checkboxChangeSpy); + + const mockTarget = { checked: true } as HTMLInputElement; + + // act + fixture.componentInstance.checkboxChanged(mockTarget, "google.com"); + + // assert + expect(checkboxChangeSpy).toHaveBeenCalledWith({ + applicationName: "google.com", + checked: true, + }); + expect(checkboxChangeSpy).toHaveBeenCalledTimes(1); + }); + + it("should emit checkboxChange with checked=false when checkbox is unchecked", () => { + // arrange + const checkboxChangeSpy = jest.fn(); + fixture.componentInstance.checkboxChange.subscribe(checkboxChangeSpy); + + const mockTarget = { checked: false } as HTMLInputElement; + + // act + fixture.componentInstance.checkboxChanged(mockTarget, "google.com"); + + // assert + expect(checkboxChangeSpy).toHaveBeenCalledWith({ + applicationName: "google.com", + checked: false, + }); + expect(checkboxChangeSpy).toHaveBeenCalledTimes(1); + }); + + it("should emit checkboxChange with correct applicationName for different applications", () => { + // arrange + const checkboxChangeSpy = jest.fn(); + fixture.componentInstance.checkboxChange.subscribe(checkboxChangeSpy); + + const mockTarget = { checked: true } as HTMLInputElement; + + // act + fixture.componentInstance.checkboxChanged(mockTarget, "facebook.com"); + + // assert + expect(checkboxChangeSpy).toHaveBeenCalledWith({ + applicationName: "facebook.com", + checked: true, + }); + }); + }); }); diff --git a/bitwarden_license/bit-web/src/app/dirt/access-intelligence/shared/app-table-row-scrollable-m11.component.ts b/bitwarden_license/bit-web/src/app/dirt/access-intelligence/shared/app-table-row-scrollable-m11.component.ts index a23d1855ba5..65cfb8d092e 100644 --- a/bitwarden_license/bit-web/src/app/dirt/access-intelligence/shared/app-table-row-scrollable-m11.component.ts +++ b/bitwarden_license/bit-web/src/app/dirt/access-intelligence/shared/app-table-row-scrollable-m11.component.ts @@ -1,5 +1,5 @@ import { CommonModule } from "@angular/common"; -import { ChangeDetectionStrategy, Component, input } from "@angular/core"; +import { ChangeDetectionStrategy, Component, input, output } from "@angular/core"; import { JslibModule } from "@bitwarden/angular/jslib.module"; import { MenuModule, TableDataSource, TableModule, TooltipDirective } from "@bitwarden/components"; @@ -30,7 +30,8 @@ export class AppTableRowScrollableM11Component { readonly selectedUrls = input>(); readonly openApplication = input(""); readonly showAppAtRiskMembers = input<(applicationName: string) => void>(); - readonly checkboxChange = input<(applicationName: string, $event: Event) => void>(); + readonly checkboxChange = output<{ applicationName: string; checked: boolean }>(); + readonly selectAllChange = output(); allAppsSelected(): boolean { const tableData = this.dataSource()?.filteredData; @@ -43,20 +44,13 @@ export class AppTableRowScrollableM11Component { return tableData.length > 0 && tableData.every((row) => selectedUrls.has(row.applicationName)); } + checkboxChanged(target: HTMLInputElement, applicationName: string) { + const checked = target.checked; + this.checkboxChange.emit({ applicationName, checked }); + } + selectAllChanged(target: HTMLInputElement) { const checked = target.checked; - - const tableData = this.dataSource()?.filteredData; - const selectedUrls = this.selectedUrls(); - - if (!tableData || !selectedUrls) { - return false; - } - - if (checked) { - tableData.forEach((row) => selectedUrls.add(row.applicationName)); - } else { - selectedUrls.clear(); - } + this.selectAllChange.emit(checked); } } diff --git a/bitwarden_license/bit-web/src/app/dirt/access-intelligence/shared/report-loading.component.html b/bitwarden_license/bit-web/src/app/dirt/access-intelligence/shared/report-loading.component.html index 0b5a63c8f03..c816861b623 100644 --- a/bitwarden_license/bit-web/src/app/dirt/access-intelligence/shared/report-loading.component.html +++ b/bitwarden_license/bit-web/src/app/dirt/access-intelligence/shared/report-loading.component.html @@ -10,14 +10,9 @@ >
    - -
    - - {{ stepConfig[progressStep()].message | i18n }} - - - {{ "thisMightTakeFewMinutes" | i18n }} - -
    + + + {{ stepConfig[progressStep()].message | i18n }} + diff --git a/bitwarden_license/bit-web/src/app/dirt/access-intelligence/shared/report-loading.component.ts b/bitwarden_license/bit-web/src/app/dirt/access-intelligence/shared/report-loading.component.ts index 45b28dae470..9df729b9645 100644 --- a/bitwarden_license/bit-web/src/app/dirt/access-intelligence/shared/report-loading.component.ts +++ b/bitwarden_license/bit-web/src/app/dirt/access-intelligence/shared/report-loading.component.ts @@ -6,12 +6,12 @@ import { ProgressModule } from "@bitwarden/components"; // Map of progress step to display config const ProgressStepConfig = Object.freeze({ - [ReportProgress.FetchingMembers]: { message: "fetchingMemberData", progress: 20 }, - [ReportProgress.AnalyzingPasswords]: { message: "analyzingPasswordHealth", progress: 40 }, - [ReportProgress.CalculatingRisks]: { message: "calculatingRiskScores", progress: 60 }, - [ReportProgress.GeneratingReport]: { message: "generatingReportData", progress: 80 }, - [ReportProgress.Saving]: { message: "savingReport", progress: 95 }, - [ReportProgress.Complete]: { message: "compilingInsights", progress: 100 }, + [ReportProgress.FetchingMembers]: { message: "reviewingMemberData", progress: 20 }, + [ReportProgress.AnalyzingPasswords]: { message: "analyzingPasswords", progress: 40 }, + [ReportProgress.CalculatingRisks]: { message: "calculatingRisks", progress: 60 }, + [ReportProgress.GeneratingReport]: { message: "generatingReports", progress: 80 }, + [ReportProgress.Saving]: { message: "compilingInsightsProgress", progress: 95 }, + [ReportProgress.Complete]: { message: "reportGenerationDone", progress: 100 }, } as const); // FIXME(https://bitwarden.atlassian.net/browse/CL-764): Migrate to OnPush diff --git a/bitwarden_license/bit-web/src/app/dirt/reports/member-access-report/member-access-report.component.html b/bitwarden_license/bit-web/src/app/dirt/reports/member-access-report/member-access-report.component.html index 440e955a226..6769998e2c8 100644 --- a/bitwarden_license/bit-web/src/app/dirt/reports/member-access-report/member-access-report.component.html +++ b/bitwarden_license/bit-web/src/app/dirt/reports/member-access-report/member-access-report.component.html @@ -9,7 +9,7 @@ > } @@ -22,11 +22,11 @@ @if (isLoading) {
    - +

    {{ "loading" | i18n }}

    } @else { @@ -42,7 +42,13 @@
    - +
    - - - {{ "loading" | i18n }} - - -

    {{ "noEventsInList" | i18n }}

    - - - - {{ "timestamp" | i18n }} - {{ "client" | i18n }} - {{ "event" | i18n }} - - - - - {{ e.date | date: "medium" }} - - {{ e.appName }} - - - - - - -
    +@if (!loaded()) { + + + {{ "loading" | i18n }} + +} +@if (loaded()) { + + @if (!events() || !events().length) { +

    {{ "noEventsInList" | i18n }}

    + } + @if (events() && events().length) { + + + + {{ "timestamp" | i18n }} + {{ "client" | i18n }} + {{ "event" | i18n }} + + + + @for (e of events(); track i; let i = $index) { + + {{ e.date | date: "medium" }} + + {{ e.appName }} + + + + } + + + } + @if (continuationToken) { + + } +
    +} diff --git a/bitwarden_license/bit-web/src/app/secrets-manager/service-accounts/event-logs/service-accounts-events.component.ts b/bitwarden_license/bit-web/src/app/secrets-manager/service-accounts/event-logs/service-accounts-events.component.ts index 5968933064d..525d658f233 100644 --- a/bitwarden_license/bit-web/src/app/secrets-manager/service-accounts/event-logs/service-accounts-events.component.ts +++ b/bitwarden_license/bit-web/src/app/secrets-manager/service-accounts/event-logs/service-accounts-events.component.ts @@ -1,6 +1,6 @@ // FIXME: Update this file to be type safe and remove this and next line // @ts-strict-ignore -import { Component, OnDestroy, OnInit } from "@angular/core"; +import { ChangeDetectionStrategy, Component, OnDestroy, OnInit } from "@angular/core"; import { ActivatedRoute } from "@angular/router"; import { takeUntil } from "rxjs"; @@ -17,9 +17,8 @@ import { EventExportService } from "@bitwarden/web-vault/app/tools/event-export" import { ServiceAccountEventLogApiService } from "./service-account-event-log-api.service"; -// FIXME(https://bitwarden.atlassian.net/browse/CL-764): Migrate to OnPush -// eslint-disable-next-line @angular-eslint/prefer-on-push-component-change-detection @Component({ + changeDetection: ChangeDetectionStrategy.OnPush, selector: "sm-service-accounts-events", templateUrl: "./service-accounts-events.component.html", standalone: false, @@ -69,7 +68,7 @@ export class ServiceAccountEventsComponent async load() { await this.refreshEvents(); - this.loaded = true; + this.loaded.set(true); } protected requestEvents(startDate: string, endDate: string, continuationToken: string) { diff --git a/eslint.config.mjs b/eslint.config.mjs index 974aaafeef6..2e35b011c73 100644 --- a/eslint.config.mjs +++ b/eslint.config.mjs @@ -208,6 +208,7 @@ export default tseslint.config( { ignoreIfHas: ["bitPasswordInputToggle"] }, ], "@bitwarden/components/no-bwi-class-usage": "warn", + "@bitwarden/components/no-icon-children-in-bit-button": "warn", }, }, diff --git a/jest.config.js b/jest.config.js index bfe447f7a53..5ea699febff 100644 --- a/jest.config.js +++ b/jest.config.js @@ -61,6 +61,7 @@ module.exports = { "/libs/vault/jest.config.js", "/libs/auto-confirm/jest.config.js", "/libs/subscription/jest.config.js", + "/libs/user-crypto-management/jest.config.js", ], // Workaround for a memory leak that crashes tests in CI: 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 72187b2a2fc..4c7e0bcb92d 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 @@ -18,7 +18,7 @@
    - +
    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 36edf6dd336..3e3555cee13 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 @@ -7,6 +7,7 @@ import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.servic import { BadgeModule, ButtonModule, + IconModule, LinkModule, TableDataSource, TableModule, @@ -21,7 +22,15 @@ import { DeviceDisplayData } from "./device-management.component"; standalone: true, selector: "auth-device-management-table", templateUrl: "./device-management-table.component.html", - imports: [BadgeModule, ButtonModule, CommonModule, JslibModule, LinkModule, TableModule], + imports: [ + BadgeModule, + ButtonModule, + CommonModule, + IconModule, + JslibModule, + LinkModule, + TableModule, + ], }) export class DeviceManagementTableComponent implements OnChanges { // FIXME(https://bitwarden.atlassian.net/browse/CL-903): Migrate to Signals 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 2a91c2daae2..1b113082254 100644 --- a/libs/angular/src/auth/device-management/device-management.component.html +++ b/libs/angular/src/auth/device-management/device-management.component.html @@ -8,7 +8,7 @@ [bitPopoverTriggerFor]="infoPopover" position="right-start" > - + @@ -23,7 +23,11 @@ @if (initializing) {
    - +
    } @else { 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 d8f8cc10df4..c697ea44099 100644 --- a/libs/angular/src/auth/device-management/device-management.component.ts +++ b/libs/angular/src/auth/device-management/device-management.component.ts @@ -19,7 +19,7 @@ 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, DialogService, PopoverModule } from "@bitwarden/components"; +import { ButtonModule, DialogService, IconModule, PopoverModule } from "@bitwarden/components"; import { I18nPipe } from "@bitwarden/ui-common"; import { LoginApprovalDialogComponent } from "../login-approval"; @@ -62,6 +62,7 @@ export interface DeviceDisplayData { DeviceManagementItemGroupComponent, DeviceManagementTableComponent, I18nPipe, + IconModule, PopoverModule, ], }) diff --git a/libs/angular/src/auth/environment-selector/environment-selector.component.html b/libs/angular/src/auth/environment-selector/environment-selector.component.html index 72d7355c399..a1115d94712 100644 --- a/libs/angular/src/auth/environment-selector/environment-selector.component.html +++ b/libs/angular/src/auth/environment-selector/environment-selector.component.html @@ -12,12 +12,12 @@ [attr.aria-pressed]="data.selectedRegion === region ? 'true' : 'false'" (click)="toggle(region.key)" > - + > {{ region.domain }} @@ -41,7 +41,7 @@ {{ data.selectedRegion?.domain || ("selfHostedServer" | 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 index 89366f47b70..79df6a2d992 100644 --- a/libs/angular/src/auth/environment-selector/environment-selector.component.ts +++ b/libs/angular/src/auth/environment-selector/environment-selector.component.ts @@ -13,6 +13,7 @@ import { import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { DialogService, + IconModule, LinkModule, MenuModule, ToastService, @@ -26,7 +27,7 @@ import { I18nPipe } from "@bitwarden/ui-common"; selector: "environment-selector", templateUrl: "environment-selector.component.html", standalone: true, - imports: [CommonModule, I18nPipe, MenuModule, LinkModule, TypographyModule], + imports: [CommonModule, I18nPipe, IconModule, LinkModule, MenuModule, TypographyModule], }) export class EnvironmentSelectorComponent implements OnDestroy { protected ServerEnvironmentType = Region; diff --git a/libs/angular/src/auth/login-approval/login-approval-dialog.component.html b/libs/angular/src/auth/login-approval/login-approval-dialog.component.html index f2850406235..fdaf6584251 100644 --- a/libs/angular/src/auth/login-approval/login-approval-dialog.component.html +++ b/libs/angular/src/auth/login-approval/login-approval-dialog.component.html @@ -4,7 +4,11 @@
    - +
    diff --git a/libs/angular/src/auth/login-approval/login-approval-dialog.component.ts b/libs/angular/src/auth/login-approval/login-approval-dialog.component.ts index 54906047535..36e553aa7d9 100644 --- a/libs/angular/src/auth/login-approval/login-approval-dialog.component.ts +++ b/libs/angular/src/auth/login-approval/login-approval-dialog.component.ts @@ -20,6 +20,7 @@ import { ButtonModule, DialogModule, DialogService, + IconModule, ToastService, } from "@bitwarden/components"; import { LogService } from "@bitwarden/logging"; @@ -35,7 +36,7 @@ export interface LoginApprovalDialogParams { // eslint-disable-next-line @angular-eslint/prefer-on-push-component-change-detection @Component({ templateUrl: "login-approval-dialog.component.html", - imports: [AsyncActionsModule, ButtonModule, CommonModule, DialogModule, JslibModule], + imports: [AsyncActionsModule, ButtonModule, CommonModule, DialogModule, IconModule, JslibModule], }) export class LoginApprovalDialogComponent implements OnInit, OnDestroy { authRequestId: string; diff --git a/libs/angular/src/auth/password-management/change-password/change-password.component.html b/libs/angular/src/auth/password-management/change-password/change-password.component.html index 7604ffacea7..c147af329ce 100644 --- a/libs/angular/src/auth/password-management/change-password/change-password.component.html +++ b/libs/angular/src/auth/password-management/change-password/change-password.component.html @@ -1,10 +1,9 @@ @if (initializing) { - - {{ "loading" | i18n }} + } @else { - + } @else { @if (userType === SetInitialPasswordUserType.OFFBOARDED_TDE_ORG_USER_UNTRUSTED_DEVICE) { diff --git a/libs/angular/src/auth/password-management/set-initial-password/set-initial-password.component.ts b/libs/angular/src/auth/password-management/set-initial-password/set-initial-password.component.ts index 3cafbdb8ff8..1680bf57720 100644 --- a/libs/angular/src/auth/password-management/set-initial-password/set-initial-password.component.ts +++ b/libs/angular/src/auth/password-management/set-initial-password/set-initial-password.component.ts @@ -37,6 +37,7 @@ import { ButtonModule, CalloutComponent, DialogService, + IconModule, ToastService, } from "@bitwarden/components"; import { KeyService } from "@bitwarden/key-management"; @@ -56,7 +57,14 @@ import { @Component({ standalone: true, templateUrl: "set-initial-password.component.html", - imports: [ButtonModule, CalloutComponent, CommonModule, InputPasswordComponent, I18nPipe], + imports: [ + ButtonModule, + CalloutComponent, + CommonModule, + IconModule, + InputPasswordComponent, + I18nPipe, + ], }) export class SetInitialPasswordComponent implements OnInit { protected inputPasswordFlow = InputPasswordFlow.SetInitialPasswordAuthedUser; diff --git a/libs/angular/src/services/jslib-services.module.ts b/libs/angular/src/services/jslib-services.module.ts index 9d407f0f310..51e78c2627e 100644 --- a/libs/angular/src/services/jslib-services.module.ts +++ b/libs/angular/src/services/jslib-services.module.ts @@ -1528,7 +1528,7 @@ const safeProviders: SafeProvider[] = [ safeProvider({ provide: OrganizationMetadataServiceAbstraction, useClass: DefaultOrganizationMetadataService, - deps: [BillingApiServiceAbstraction, ConfigService, PlatformUtilsServiceAbstraction], + deps: [BillingApiServiceAbstraction, PlatformUtilsServiceAbstraction], }), safeProvider({ provide: BillingAccountProfileStateService, @@ -1685,7 +1685,7 @@ const safeProviders: SafeProvider[] = [ AccountServiceAbstraction, KdfConfigService, KeyService, - SecurityStateService, + AccountCryptographicStateService, ApiServiceAbstraction, StateProvider, ConfigService, diff --git a/libs/auth/src/angular/fingerprint-dialog/fingerprint-dialog.component.html b/libs/auth/src/angular/fingerprint-dialog/fingerprint-dialog.component.html index d66a3a77d93..ed43a32d38c 100644 --- a/libs/auth/src/angular/fingerprint-dialog/fingerprint-dialog.component.html +++ b/libs/auth/src/angular/fingerprint-dialog/fingerprint-dialog.component.html @@ -1,5 +1,5 @@ - + {{ "yourAccountsFingerprint" | i18n }}: @@ -16,7 +16,7 @@ bitDialogClose > {{ "learnMore" | i18n }} - +
    @@ -78,7 +78,7 @@ [buttonType]="ssoRequired ? 'primary' : 'secondary'" (click)="handleSsoClick()" > - + {{ "useSingleSignOn" | i18n }} @@ -114,7 +114,7 @@ buttonType="secondary" (click)="startAuthRequestLogin()" > - + {{ "loginWithDevice" | i18n }}
    diff --git a/libs/auth/src/angular/login/login.component.ts b/libs/auth/src/angular/login/login.component.ts index 8e688f3f830..9957c77ffaf 100644 --- a/libs/auth/src/angular/login/login.component.ts +++ b/libs/auth/src/angular/login/login.component.ts @@ -52,6 +52,7 @@ import { CheckboxModule, FormFieldModule, IconButtonModule, + IconModule, LinkModule, ToastService, TooltipDirective, @@ -79,6 +80,7 @@ export enum LoginUiState { CommonModule, FormFieldModule, IconButtonModule, + IconModule, LinkModule, JslibModule, ReactiveFormsModule, diff --git a/libs/auth/src/angular/registration/registration-finish/registration-finish.component.html b/libs/auth/src/angular/registration/registration-finish/registration-finish.component.html index aa6b5c8edc3..031fec5c403 100644 --- a/libs/auth/src/angular/registration/registration-finish/registration-finish.component.html +++ b/libs/auth/src/angular/registration/registration-finish/registration-finish.component.html @@ -1,5 +1,5 @@
    - +
    (); diff --git a/libs/auth/src/angular/self-hosted-env-config-dialog/self-hosted-env-config-dialog.component.html b/libs/auth/src/angular/self-hosted-env-config-dialog/self-hosted-env-config-dialog.component.html index 92c2f9f2f7a..bf40b15b5da 100644 --- a/libs/auth/src/angular/self-hosted-env-config-dialog/self-hosted-env-config-dialog.component.html +++ b/libs/auth/src/angular/self-hosted-env-config-dialog/self-hosted-env-config-dialog.component.html @@ -16,11 +16,10 @@ @@ -91,7 +90,7 @@ aria-live="assertive" role="alert" > - {{ "selfHostedEnvFormInvalid" | i18n }} + {{ "selfHostedEnvFormInvalid" | i18n }}
    diff --git a/libs/auth/src/angular/self-hosted-env-config-dialog/self-hosted-env-config-dialog.component.ts b/libs/auth/src/angular/self-hosted-env-config-dialog/self-hosted-env-config-dialog.component.ts index 6fb40179afa..6e093a423b3 100644 --- a/libs/auth/src/angular/self-hosted-env-config-dialog/self-hosted-env-config-dialog.component.ts +++ b/libs/auth/src/angular/self-hosted-env-config-dialog/self-hosted-env-config-dialog.component.ts @@ -27,6 +27,7 @@ import { DialogModule, DialogService, FormFieldModule, + IconModule, LinkModule, TypographyModule, } from "@bitwarden/components"; @@ -85,6 +86,7 @@ function onlyHttpsValidator(): ValidatorFn { JslibModule, DialogModule, ButtonModule, + IconModule, LinkModule, TypographyModule, ReactiveFormsModule, diff --git a/libs/auth/src/angular/sso/sso.component.html b/libs/auth/src/angular/sso/sso.component.html index be38f63987e..9ab11b0d094 100644 --- a/libs/auth/src/angular/sso/sso.component.html +++ b/libs/auth/src/angular/sso/sso.component.html @@ -1,6 +1,6 @@
    - + {{ "loading" | i18n }}
    diff --git a/libs/auth/src/angular/sso/sso.component.ts b/libs/auth/src/angular/sso/sso.component.ts index f5167cb84cc..df358e89107 100644 --- a/libs/auth/src/angular/sso/sso.component.ts +++ b/libs/auth/src/angular/sso/sso.component.ts @@ -42,6 +42,7 @@ import { CheckboxModule, FormFieldModule, IconButtonModule, + IconModule, LinkModule, ToastService, } from "@bitwarden/components"; @@ -73,6 +74,7 @@ interface QueryParams { CommonModule, FormFieldModule, IconButtonModule, + IconModule, LinkModule, JslibModule, ReactiveFormsModule, diff --git a/libs/auth/src/angular/two-factor-auth/child-components/two-factor-auth-webauthn/two-factor-auth-webauthn.component.html b/libs/auth/src/angular/two-factor-auth/child-components/two-factor-auth-webauthn/two-factor-auth-webauthn.component.html index 6f13b0a1fe2..4c23ab4af5c 100644 --- a/libs/auth/src/angular/two-factor-auth/child-components/two-factor-auth-webauthn/two-factor-auth-webauthn.component.html +++ b/libs/auth/src/angular/two-factor-auth/child-components/two-factor-auth-webauthn/two-factor-auth-webauthn.component.html @@ -1,6 +1,6 @@
    - +