diff --git a/.github/renovate.json5 b/.github/renovate.json5 index c4202ed2a68..91b4ac86328 100644 --- a/.github/renovate.json5 +++ b/.github/renovate.json5 @@ -49,6 +49,7 @@ "./github/workflows/release-web.yml", ], commitMessagePrefix: "[deps] BRE:", + addLabels: ["hold"], }, { // Disable major and minor updates for TypeScript and Zone.js because they are managed by Angular. @@ -148,6 +149,8 @@ { matchPackageNames: [ "@angular-eslint/schematics", + "@typescript-eslint/rule-tester", + "@typescript-eslint/utils", "angular-eslint", "eslint-config-prettier", "eslint-import-resolver-typescript", @@ -312,8 +315,6 @@ "@storybook/angular", "@storybook/manager-api", "@storybook/theming", - "@typescript-eslint/utils", - "@typescript-eslint/rule-tester", "@types/react", "autoprefixer", "bootstrap", diff --git a/.github/workflows/build-cli.yml b/.github/workflows/build-cli.yml index e113d5c253b..e89ca59a297 100644 --- a/.github/workflows/build-cli.yml +++ b/.github/workflows/build-cli.yml @@ -12,12 +12,13 @@ on: - 'cf-pages' paths: - 'apps/cli/**' + - 'bitwarden_license/bit-cli/**' + - 'bitwarden_license/bit-common/**' - 'libs/**' - '*' - '!*.md' - '!*.txt' - '.github/workflows/build-cli.yml' - - 'bitwarden_license/bit-cli/**' push: branches: - 'main' @@ -25,12 +26,13 @@ on: - 'hotfix-rc-cli' paths: - 'apps/cli/**' + - 'bitwarden_license/bit-cli/**' + - 'bitwarden_license/bit-common/**' - 'libs/**' - '*' - '!*.md' - '!*.txt' - '.github/workflows/build-cli.yml' - - 'bitwarden_license/bit-cli/**' workflow_call: inputs: {} workflow_dispatch: diff --git a/.github/workflows/build-desktop.yml b/.github/workflows/build-desktop.yml index 365d29f17f7..86dc74f7351 100644 --- a/.github/workflows/build-desktop.yml +++ b/.github/workflows/build-desktop.yml @@ -92,6 +92,7 @@ jobs: id: retrieve-version run: | PKG_VERSION=$(jq -r .version src/package.json) + echo "Setting version number to $PKG_VERSION" echo "package_version=$PKG_VERSION" >> $GITHUB_OUTPUT - name: Increment Version @@ -725,6 +726,11 @@ jobs: --file $HOME/secrets/bitwarden_desktop_appstore.provisionprofile \ --output none + az storage blob download --account-name $ACCOUNT_NAME --container-name $CONTAINER_NAME \ + --name bitwarden_desktop_autofill_app_store_2024.provisionprofile \ + --file $HOME/secrets/bitwarden_desktop_autofill_app_store_2024.provisionprofile \ + --output none + - name: Get certificates if: ${{ needs.setup.outputs.has_secrets == 'true' }} run: | @@ -784,6 +790,15 @@ jobs: cp $HOME/secrets/bitwarden_desktop_appstore.provisionprofile \ $GITHUB_WORKSPACE/apps/desktop/bitwarden_desktop_appstore.provisionprofile + mkdir -p $HOME/Library/MobileDevice/Provisioning\ Profiles + export APP_UUID=`grep UUID -A1 -a $HOME/secrets/bitwarden_desktop_appstore.provisionprofile | grep -io "[-A-Z0-9]\{36\}"` + export AUTOFILL_UUID=`grep UUID -A1 -a $HOME/secrets/bitwarden_desktop_autofill_app_store_2024.provisionprofile | grep -io "[-A-Z0-9]\{36\}"` + + cp $HOME/secrets/bitwarden_desktop_appstore.provisionprofile \ + $HOME/Library/MobileDevice/Provisioning\ Profiles/$APP_UUID.provisionprofile + cp $HOME/secrets/bitwarden_desktop_autofill_app_store_2024.provisionprofile \ + $HOME/Library/MobileDevice/Provisioning\ Profiles/$AUTOFILL_UUID.provisionprofile + - name: Increment version shell: pwsh env: @@ -914,8 +929,13 @@ jobs: mkdir -p $HOME/secrets az storage blob download --account-name $ACCOUNT_NAME --container-name $CONTAINER_NAME \ - --name bitwarden_desktop_appstore.provisionprofile \ - --file $HOME/secrets/bitwarden_desktop_appstore.provisionprofile \ + --name bitwarden_desktop_developer_id.provisionprofile \ + --file $HOME/secrets/bitwarden_desktop_developer_id.provisionprofile \ + --output none + + az storage blob download --account-name $ACCOUNT_NAME --container-name $CONTAINER_NAME \ + --name bitwarden_desktop_autofill_developer_id.provisionprofile \ + --file $HOME/secrets/bitwarden_desktop_autofill_developer_id.provisionprofile \ --output none - name: Get certificates @@ -958,21 +978,21 @@ jobs: security import "$HOME/certificates/devid-installer-cert.p12" -k build.keychain -P "" \ -T /usr/bin/codesign -T /usr/bin/security -T /usr/bin/productbuild - security import "$HOME/certificates/appstore-app-cert.p12" -k build.keychain -P "" \ - -T /usr/bin/codesign -T /usr/bin/security -T /usr/bin/productbuild - - security import "$HOME/certificates/appstore-installer-cert.p12" -k build.keychain -P "" \ - -T /usr/bin/codesign -T /usr/bin/security -T /usr/bin/productbuild - - security import "$HOME/certificates/macdev-cert.p12" -k build.keychain -P "" \ - -T /usr/bin/codesign -T /usr/bin/security -T /usr/bin/productbuild - security set-key-partition-list -S apple-tool:,apple:,codesign: -s -k $KEYCHAIN_PASSWORD build.keychain - name: Set up provisioning profiles run: | - cp $HOME/secrets/bitwarden_desktop_appstore.provisionprofile \ - $GITHUB_WORKSPACE/apps/desktop/bitwarden_desktop_appstore.provisionprofile + cp $HOME/secrets/bitwarden_desktop_developer_id.provisionprofile \ + $GITHUB_WORKSPACE/apps/desktop/bitwarden_desktop_developer_id.provisionprofile + + mkdir -p $HOME/Library/MobileDevice/Provisioning\ Profiles + export APP_UUID=`grep UUID -A1 -a $HOME/secrets/bitwarden_desktop_developer_id.provisionprofile | grep -io "[-A-Z0-9]\{36\}"` + export AUTOFILL_UUID=`grep UUID -A1 -a $HOME/secrets/bitwarden_desktop_autofill_developer_id.provisionprofile | grep -io "[-A-Z0-9]\{36\}"` + + cp $HOME/secrets/bitwarden_desktop_developer_id.provisionprofile \ + $HOME/Library/MobileDevice/Provisioning\ Profiles/$APP_UUID.provisionprofile + cp $HOME/secrets/bitwarden_desktop_autofill_developer_id.provisionprofile \ + $HOME/Library/MobileDevice/Provisioning\ Profiles/$AUTOFILL_UUID.provisionprofile - name: Increment version shell: pwsh @@ -1020,7 +1040,9 @@ jobs: - name: Build Native Module if: steps.cache.outputs.cache-hit != 'true' working-directory: apps/desktop/desktop_native - run: node build.js cross-platform + run: | + rustup target add aarch64-apple-darwin + node build.js cross-platform - name: Build if: steps.build-cache.outputs.cache-hit != 'true' @@ -1167,6 +1189,11 @@ jobs: --file $HOME/secrets/bitwarden_desktop_appstore.provisionprofile \ --output none + az storage blob download --account-name $ACCOUNT_NAME --container-name $CONTAINER_NAME \ + --name bitwarden_desktop_autofill_app_store_2024.provisionprofile \ + --file $HOME/secrets/bitwarden_desktop_autofill_app_store_2024.provisionprofile \ + --output none + - name: Get certificates run: | mkdir -p $HOME/certificates @@ -1201,21 +1228,12 @@ jobs: security import "$HOME/certificates/bitwarden-desktop-key.p12" -k build.keychain -P "" \ -T /usr/bin/codesign -T /usr/bin/security -T /usr/bin/productbuild - security import "$HOME/certificates/devid-app-cert.p12" -k build.keychain -P "" \ - -T /usr/bin/codesign -T /usr/bin/security -T /usr/bin/productbuild - - security import "$HOME/certificates/devid-installer-cert.p12" -k build.keychain -P "" \ - -T /usr/bin/codesign -T /usr/bin/security -T /usr/bin/productbuild - security import "$HOME/certificates/appstore-app-cert.p12" -k build.keychain -P "" \ -T /usr/bin/codesign -T /usr/bin/security -T /usr/bin/productbuild security import "$HOME/certificates/appstore-installer-cert.p12" -k build.keychain -P "" \ -T /usr/bin/codesign -T /usr/bin/security -T /usr/bin/productbuild - security import "$HOME/certificates/macdev-cert.p12" -k build.keychain -P "" \ - -T /usr/bin/codesign -T /usr/bin/security -T /usr/bin/productbuild - security set-key-partition-list -S apple-tool:,apple:,codesign: -s -k $KEYCHAIN_PASSWORD build.keychain - name: Set up provisioning profiles @@ -1223,6 +1241,15 @@ jobs: cp $HOME/secrets/bitwarden_desktop_appstore.provisionprofile \ $GITHUB_WORKSPACE/apps/desktop/bitwarden_desktop_appstore.provisionprofile + mkdir -p $HOME/Library/MobileDevice/Provisioning\ Profiles + export APP_UUID=`grep UUID -A1 -a $HOME/secrets/bitwarden_desktop_appstore.provisionprofile | grep -io "[-A-Z0-9]\{36\}"` + export AUTOFILL_UUID=`grep UUID -A1 -a $HOME/secrets/bitwarden_desktop_autofill_app_store_2024.provisionprofile | grep -io "[-A-Z0-9]\{36\}"` + + cp $HOME/secrets/bitwarden_desktop_appstore.provisionprofile \ + $HOME/Library/MobileDevice/Provisioning\ Profiles/$APP_UUID.provisionprofile + cp $HOME/secrets/bitwarden_desktop_autofill_app_store_2024.provisionprofile \ + $HOME/Library/MobileDevice/Provisioning\ Profiles/$AUTOFILL_UUID.provisionprofile + - name: Increment version shell: pwsh env: @@ -1269,7 +1296,9 @@ jobs: - name: Build Native Module if: steps.cache.outputs.cache-hit != 'true' working-directory: apps/desktop/desktop_native - run: node build.js cross-platform + run: | + rustup target add aarch64-apple-darwin + node build.js cross-platform - name: Build if: steps.build-cache.outputs.cache-hit != 'true' @@ -1378,226 +1407,6 @@ jobs: env: BUILD_NUMBER: ${{ needs.setup.outputs.build_number }} - - macos-package-dev: - name: MacOS Package Dev Release Asset - runs-on: macos-13 - if: ${{ needs.setup.outputs.has_secrets == 'true' }} - needs: - - browser-build - - macos-build - - setup - env: - _PACKAGE_VERSION: ${{ needs.setup.outputs.package_version }} - _NODE_VERSION: ${{ needs.setup.outputs.node_version }} - NODE_OPTIONS: --max_old_space_size=4096 - defaults: - run: - working-directory: apps/desktop - steps: - - name: Check out repo - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - with: - ref: ${{ github.event.pull_request.head.sha }} - - - name: Set up Node - uses: actions/setup-node@39370e3970a6d050c480ffad4ff0ed4d3fdee5af # v4.1.0 - with: - cache: 'npm' - cache-dependency-path: '**/package-lock.json' - node-version: ${{ env._NODE_VERSION }} - - - name: Set up Node-gyp - run: python3 -m pip install setuptools - - - name: Print environment - run: | - node --version - npm --version - echo "GitHub ref: $GITHUB_REF" - echo "GitHub event: $GITHUB_EVENT" - - - name: Get Build Cache - id: build-cache - uses: actions/cache@1bd1e32a3bdc45362d1e726936510720a7c30a57 # v4.2.0 - with: - path: apps/desktop/build - key: ${{ runner.os }}-${{ github.run_id }}-build - - - name: Setup Safari Cache - id: safari-cache - uses: actions/cache@1bd1e32a3bdc45362d1e726936510720a7c30a57 # v4.2.0 - with: - path: apps/browser/dist/Safari - key: ${{ runner.os }}-${{ github.run_id }}-safari-extension - - - name: Login to Azure - uses: Azure/login@e15b166166a8746d1a47596803bd8c1b595455cf # v1.6.0 - with: - creds: ${{ secrets.AZURE_KV_CI_SERVICE_PRINCIPAL }} - - - name: Download Provisioning Profiles secrets - env: - ACCOUNT_NAME: bitwardenci - CONTAINER_NAME: profiles - run: | - mkdir -p $HOME/secrets - - az storage blob download --account-name $ACCOUNT_NAME --container-name $CONTAINER_NAME \ - --name bitwarden_desktop_appstore.provisionprofile \ - --file $HOME/secrets/bitwarden_desktop_appstore.provisionprofile \ - --output none - - - name: Get certificates - run: | - mkdir -p $HOME/certificates - - az keyvault secret show --id https://bitwarden-ci.vault.azure.net/certificates/bitwarden-desktop-key | - jq -r .value | base64 -d > $HOME/certificates/bitwarden-desktop-key.p12 - - az keyvault secret show --id https://bitwarden-ci.vault.azure.net/certificates/appstore-app-cert | - jq -r .value | base64 -d > $HOME/certificates/appstore-app-cert.p12 - - az keyvault secret show --id https://bitwarden-ci.vault.azure.net/certificates/appstore-installer-cert | - jq -r .value | base64 -d > $HOME/certificates/appstore-installer-cert.p12 - - az keyvault secret show --id https://bitwarden-ci.vault.azure.net/certificates/devid-app-cert | - jq -r .value | base64 -d > $HOME/certificates/devid-app-cert.p12 - - az keyvault secret show --id https://bitwarden-ci.vault.azure.net/certificates/devid-installer-cert | - jq -r .value | base64 -d > $HOME/certificates/devid-installer-cert.p12 - - az keyvault secret show --id https://bitwarden-ci.vault.azure.net/certificates/macdev-cert | - jq -r .value | base64 -d > $HOME/certificates/macdev-cert.p12 - - - name: Set up keychain - env: - KEYCHAIN_PASSWORD: ${{ secrets.KEYCHAIN_PASSWORD }} - run: | - security create-keychain -p $KEYCHAIN_PASSWORD build.keychain - security default-keychain -s build.keychain - security unlock-keychain -p $KEYCHAIN_PASSWORD build.keychain - security set-keychain-settings -lut 1200 build.keychain - - security import "$HOME/certificates/bitwarden-desktop-key.p12" -k build.keychain -P "" \ - -T /usr/bin/codesign -T /usr/bin/security -T /usr/bin/productbuild - - security import "$HOME/certificates/devid-app-cert.p12" -k build.keychain -P "" \ - -T /usr/bin/codesign -T /usr/bin/security -T /usr/bin/productbuild - - security import "$HOME/certificates/devid-installer-cert.p12" -k build.keychain -P "" \ - -T /usr/bin/codesign -T /usr/bin/security -T /usr/bin/productbuild - - security import "$HOME/certificates/appstore-app-cert.p12" -k build.keychain -P "" \ - -T /usr/bin/codesign -T /usr/bin/security -T /usr/bin/productbuild - - security import "$HOME/certificates/appstore-installer-cert.p12" -k build.keychain -P "" \ - -T /usr/bin/codesign -T /usr/bin/security -T /usr/bin/productbuild - - security import "$HOME/certificates/macdev-cert.p12" -k build.keychain -P "" \ - -T /usr/bin/codesign -T /usr/bin/security -T /usr/bin/productbuild - - security set-key-partition-list -S apple-tool:,apple:,codesign: -s -k $KEYCHAIN_PASSWORD build.keychain - - - name: Set up provisioning profiles - run: | - cp $HOME/secrets/bitwarden_desktop_appstore.provisionprofile \ - $GITHUB_WORKSPACE/apps/desktop/bitwarden_desktop_appstore.provisionprofile - - - name: Increment version - shell: pwsh - env: - BUILD_NUMBER: ${{ needs.setup.outputs.build_number }} - run: | - $package = Get-Content -Raw -Path electron-builder.json | ConvertFrom-Json - $package | Add-Member -MemberType NoteProperty -Name buildVersion -Value "$env:BUILD_NUMBER" - $package | ConvertTo-Json -Depth 32 | Set-Content -Path electron-builder.json - Write-Output "### MacOS Dev build number: $env:BUILD_NUMBER" - - - name: Install Node dependencies - run: npm ci - working-directory: ./ - - - name: Download SDK Artifacts - if: ${{ inputs.sdk_branch != '' }} - uses: bitwarden/gh-actions/download-artifacts@main - with: - github_token: ${{ secrets.GITHUB_TOKEN }} - workflow: build-wasm-internal.yml - workflow_conclusion: success - branch: ${{ inputs.sdk_branch }} - artifacts: sdk-internal - repo: bitwarden/sdk-internal - path: ../sdk-internal - if_no_artifact_found: fail - - - name: Override SDK - if: ${{ inputs.sdk_branch != '' }} - working-directory: ./ - run: | - ls -l ../ - npm link ../sdk-internal - - - name: Cache Native Module - uses: actions/cache@1bd1e32a3bdc45362d1e726936510720a7c30a57 # v4.2.0 - id: cache - with: - path: | - apps/desktop/desktop_native/napi/*.node - apps/desktop/desktop_native/dist/* - key: rust-${{ runner.os }}-${{ hashFiles('apps/desktop/desktop_native/**/*') }} - - - name: Build Native Module - if: steps.cache.outputs.cache-hit != 'true' - working-directory: apps/desktop/desktop_native - run: node build.js cross-platform - - - name: Build - if: steps.build-cache.outputs.cache-hit != 'true' - run: npm run build - - - name: Download Browser artifact - uses: actions/download-artifact@fa0a91b85d4f404e444e00e005971372dc801d16 # v4.1.8 - with: - path: ${{ github.workspace }}/browser-build-artifacts - - - name: Unzip Safari artifact - run: | - SAFARI_DIR=$(find $GITHUB_WORKSPACE/browser-build-artifacts -name 'dist-safari-*.zip') - echo $SAFARI_DIR - unzip $SAFARI_DIR/dist-safari.zip -d $GITHUB_WORKSPACE/browser-build-artifacts - - - name: Load Safari extension for App Store - run: | - mkdir PlugIns - cp -r $GITHUB_WORKSPACE/browser-build-artifacts/Safari/masdev/build/Release/safari.appex PlugIns/safari.appex - - - name: Set up private auth key - run: | - mkdir ~/private_keys - cat << EOF > ~/private_keys/AuthKey_6TV9MKN3GP.p8 - ${{ secrets.APP_STORE_CONNECT_AUTH_KEY }} - EOF - - - name: Build dev application for App Store - env: - APP_STORE_CONNECT_TEAM_ISSUER: ${{ secrets.APP_STORE_CONNECT_TEAM_ISSUER }} - APP_STORE_CONNECT_AUTH_KEY_PATH: ~/private_keys/AuthKey_6TV9MKN3GP.p8 - run: npm run pack:mac:masdev - - - name: Zip masdev asset - run: | - cd dist/mas-dev-universal - zip -r Bitwarden-${{ env._PACKAGE_VERSION }}-masdev-universal.zip Bitwarden.app - - - name: Upload masdev artifact - uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4.6.0 - with: - name: Bitwarden-${{ env._PACKAGE_VERSION }}-masdev-universal.zip - path: apps/desktop/dist/mas-dev-universal/Bitwarden-${{ env._PACKAGE_VERSION }}-masdev-universal.zip - if-no-files-found: error - - crowdin-push: name: Crowdin Push if: github.event_name != 'pull_request_target' && github.ref == 'refs/heads/main' diff --git a/.github/workflows/build-web.yml b/.github/workflows/build-web.yml index 3da524702fe..630e1e55682 100644 --- a/.github/workflows/build-web.yml +++ b/.github/workflows/build-web.yml @@ -12,6 +12,8 @@ on: - 'cf-pages' paths: - 'apps/web/**' + - 'bitwarden_license/bit-common/**' + - 'bitwarden_license/bit-web/**' - 'libs/**' - '*' - '!*.md' @@ -24,6 +26,8 @@ on: - 'hotfix-rc-web' paths: - 'apps/web/**' + - 'bitwarden_license/bit-common/**' + - 'bitwarden_license/bit-web/**' - 'libs/**' - '*' - '!*.md' @@ -129,12 +133,34 @@ jobs: with: ref: ${{ github.event.pull_request.head.sha }} + - name: Get Latest Server Version + id: latest-server-version + uses: bitwarden/gh-actions/get-release-version@main + with: + repository: bitwarden/server + trim: false + + - name: Set Server Ref + id: set-server-ref + run: | + SERVER_REF="${{ steps.latest-server-version.outputs.version }}" + echo "Latest server release version: $SERVER_REF" + if [[ "$GITHUB_REF" == "refs/heads/main" ]]; then + SERVER_REF="$GITHUB_REF" + elif [[ "$GITHUB_REF" == "refs/heads/rc" ]]; then + SERVER_REF="$GITHUB_REF" + elif [[ "$GITHUB_EVENT_NAME" == "pull_request" ]]; then + SERVER_REF="refs/heads/main" + fi + echo "Server ref: $SERVER_REF" + echo "server_ref=$SERVER_REF" >> $GITHUB_OUTPUT + - name: Check out Server repo uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 with: path: server repository: bitwarden/server - ref: ${{ github.event.pull_request.head.sha && 'main' || github.ref }} + ref: ${{ steps.set-server-ref.outputs.server_ref }} - name: Check Branch to Publish env: @@ -156,7 +182,7 @@ jobs: VERSION=$( jq -r ".version" package.json) jq --arg version "$VERSION+${GITHUB_SHA:0:7}" '.version = $version' package.json > package.json.tmp mv package.json.tmp package.json - + ########## Set up Docker ########## - name: Set up Docker uses: docker/setup-docker-action@b60f85385d03ac8acfca6d9996982511d8620a19 # v4.3.0 @@ -170,7 +196,7 @@ jobs: } - name: Set up QEMU emulators - uses: docker/setup-qemu-action@68827325e0b33c7199eb31dd4e31fbe9023e06e3 # v3.0.0 + uses: docker/setup-qemu-action@29109295f81e9208d7d86ff1c6c12d2833863392 # v3.6.0 - name: Set up Docker Buildx uses: docker/setup-buildx-action@f95db51fddba0c2d1ec667646a06c2ce06100226 # v3.0.0 @@ -300,7 +326,7 @@ jobs: - name: Log out of Docker run: docker logout $_AZ_REGISTRY - + crowdin-push: name: Crowdin Push if: github.event_name != 'pull_request_target' && github.ref == 'refs/heads/main' diff --git a/.vscode/settings.json b/.vscode/settings.json index 295c290a37a..8f89bc03b8c 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,5 +1,5 @@ { - "cSpell.words": ["Csprng", "decryptable", "Popout", "Reprompt", "takeuntil"], + "cSpell.words": ["Csprng", "Decapsulation", "decryptable", "Popout", "Reprompt", "takeuntil"], "search.exclude": { "**/locales/[^e]*/messages.json": true, "**/locales/*[^n]/messages.json": true, diff --git a/LICENSE.txt b/LICENSE.txt index 55bf3b3f736..8ad59f788b3 100644 --- a/LICENSE.txt +++ b/LICENSE.txt @@ -5,13 +5,13 @@ specifies another license. Bitwarden Licensed code is found only in the /bitwarden_license directory. GPL v3.0: -https://github.com/bitwarden/web/blob/master/LICENSE_GPL.txt +https://github.com/bitwarden/clients/blob/main/LICENSE_GPL.txt Bitwarden License v1.0: -https://github.com/bitwarden/web/blob/master/LICENSE_BITWARDEN.txt +https://github.com/bitwarden/clients/blob/main/LICENSE_BITWARDEN.txt No grant of any rights in the trademarks, service marks, or logos of Bitwarden is made (except as may be necessary to comply with the notice requirements as applicable), and use of any Bitwarden trademarks must comply with Bitwarden Trademark Guidelines -. +. diff --git a/LICENSE_BITWARDEN.txt b/LICENSE_BITWARDEN.txt index 08e09f28639..938946a09a1 100644 --- a/LICENSE_BITWARDEN.txt +++ b/LICENSE_BITWARDEN.txt @@ -56,7 +56,7 @@ such Open Source Software only. logos of any Contributor (except as may be necessary to comply with the notice requirements in Section 2.3), and use of any Bitwarden trademarks must comply with Bitwarden Trademark Guidelines -. +. 3. TERMINATION diff --git a/apps/browser/README.md b/apps/browser/README.md index c99d0844a09..fdeb1307567 100644 --- a/apps/browser/README.md +++ b/apps/browser/README.md @@ -1,4 +1,4 @@ -[](https://github.com/bitwarden/clients/actions/workflows/build-browser.yml?query=branch:master) +[](https://github.com/bitwarden/clients/actions/workflows/build-browser.yml?query=branch:main) [](https://crowdin.com/project/bitwarden-browser) [](https://gitter.im/bitwarden/Lobby) @@ -15,7 +15,7 @@ The Bitwarden browser extension is written using the Web Extension API and Angular. - + ## Documentation diff --git a/apps/browser/package.json b/apps/browser/package.json index b311b837e78..9ed3c807c11 100644 --- a/apps/browser/package.json +++ b/apps/browser/package.json @@ -1,6 +1,6 @@ { "name": "@bitwarden/browser", - "version": "2025.3.2", + "version": "2025.4.0", "scripts": { "build": "npm run build:chrome", "build:chrome": "cross-env BROWSER=chrome MANIFEST_VERSION=3 NODE_OPTIONS=\"--max-old-space-size=8192\" webpack", diff --git a/apps/browser/src/_locales/ar/messages.json b/apps/browser/src/_locales/ar/messages.json index 1bd6a77b746..c2f3c72f281 100644 --- a/apps/browser/src/_locales/ar/messages.json +++ b/apps/browser/src/_locales/ar/messages.json @@ -2,6 +2,9 @@ "appName": { "message": "Bitwarden" }, + "appLogoLabel": { + "message": "Bitwarden logo" + }, "extName": { "message": "مدير كلمات المرور Bitwarden", "description": "Extension name, MUST be less than 40 characters (Safari restriction)" @@ -81,7 +84,7 @@ "message": "تلميح كلمة المرور الرئيسية (إختياري)" }, "passwordStrengthScore": { - "message": "Password strength score $SCORE$", + "message": "درجة قوة كلمة المرور $SCORE$", "placeholders": { "score": { "content": "$1", @@ -186,7 +189,7 @@ "message": "نسخ الملاحظات" }, "copy": { - "message": "Copy", + "message": "نسخ", "description": "Copy to clipboard" }, "fill": { @@ -380,7 +383,7 @@ "message": "تحرير المجلّد" }, "editFolderWithName": { - "message": "Edit folder: $FOLDERNAME$", + "message": "تحرير المجلد: $FOLDERNAME$", "placeholders": { "foldername": { "content": "$1", @@ -395,7 +398,7 @@ "message": "أسم المجلد" }, "folderHintText": { - "message": "Nest a folder by adding the parent folder's name followed by a “/”. Example: Social/Forums" + "message": "قم بدمج مجلد بإضافة اسم المجلد الرئيسي متبوعًا بعلامة \"/\". مثال: اجتماعي/منتديات" }, "noFoldersAdded": { "message": "لا توجد مجلدات مضافة" @@ -462,16 +465,16 @@ "message": "توليد عبارة المرور" }, "passwordGenerated": { - "message": "Password generated" + "message": "مولد كلمة المرور" }, "passphraseGenerated": { - "message": "Passphrase generated" + "message": "تم إنشاء عبارة المرور" }, "usernameGenerated": { - "message": "Username generated" + "message": "تم إنشاء اسم المستخدم" }, "emailGenerated": { - "message": "Email generated" + "message": "تم إنشاء البريد الإلكتروني" }, "regeneratePassword": { "message": "إعادة توليد كلمة المرور" @@ -653,13 +656,13 @@ "message": "متصفح الويب الخاص بك لا يدعم خاصية النسخ السهل. يرجى استخدام النسخ اليدوي." }, "verifyYourIdentity": { - "message": "Verify your identity" + "message": "قم بتأكيد هويتك" }, "weDontRecognizeThisDevice": { - "message": "We don't recognize this device. Enter the code sent to your email to verify your identity." + "message": "لم نتعرف على هذا الجهاز. أدخل الرمز المرسل إلى بريدك الإلكتروني للتحقق من هويتك." }, "continueLoggingIn": { - "message": "Continue logging in" + "message": "متابعة تسجيل الدخول" }, "yourVaultIsLocked": { "message": "خزانتك مقفلة. قم بتأكيد هويتك للمتابعة." @@ -869,19 +872,22 @@ "message": "تسجيل الدخول إلى Bitwarden" }, "enterTheCodeSentToYourEmail": { - "message": "Enter the code sent to your email" + "message": "أدخل الرمز المرسل إلى بريدك الإلكتروني" }, "enterTheCodeFromYourAuthenticatorApp": { - "message": "Enter the code from your authenticator app" + "message": "أدخل الرمز من تطبيق المصادقة الخاص بك" }, "pressYourYubiKeyToAuthenticate": { - "message": "Press your YubiKey to authenticate" + "message": "اضغط على YubiKey الخاص بك للمصادقة" }, "duoTwoFactorRequiredPageSubtitle": { - "message": "Duo two-step login is required for your account. Follow the steps below to finish logging in." + "message": "تسجيل الدخول من خطوتين مطلوب لحسابك. اتبع الخطوات أدناه لإنهاء تسجيل الدخول." }, "followTheStepsBelowToFinishLoggingIn": { - "message": "Follow the steps below to finish logging in." + "message": "اتبع الخطوات أدناه لإنهاء تسجيل الدخول." + }, + "followTheStepsBelowToFinishLoggingInWithSecurityKey": { + "message": "Follow the steps below to finish logging in with your security key." }, "restartRegistration": { "message": "إعادة التسجيل" @@ -905,7 +911,7 @@ "message": "لا" }, "location": { - "message": "Location" + "message": "الموقع" }, "unexpectedError": { "message": "حدث خطأ غير متوقع." @@ -1010,7 +1016,7 @@ "message": "اطلب إضافة تسجيل الدخول" }, "vaultSaveOptionsTitle": { - "message": "Save to vault options" + "message": "حفظ في خيارات المخزن" }, "addLoginNotificationDesc": { "message": "اطلب إضافة عنصر إذا لم يُعثر عليه في خزانتك." @@ -1019,7 +1025,7 @@ "message": "اطلب إضافة عنصر إذا لم يتم العثور على عنصر في المخزن الخاص بك. ينطبق على جميع حسابات تسجيل الدخول." }, "showCardsInVaultViewV2": { - "message": "Always show cards as Autofill suggestions on Vault view" + "message": "إظهار البطاقات دائمًا كاقتراحات التعبئة التلقائية في عرض المخزن" }, "showCardsCurrentTab": { "message": "أظهر البطاقات في صفحة التبويبات" @@ -1028,7 +1034,7 @@ "message": "قائمة عناصر البطاقة في صفحة التبويب لسهولة التعبئة التلقائية." }, "showIdentitiesInVaultViewV2": { - "message": "Always show identities as Autofill suggestions on Vault view" + "message": "إظهار الهويات دائمًا كاقتراحات التعبئة التلقائية في عرض المخزن" }, "showIdentitiesCurrentTab": { "message": "إظهار الهويات على صفحة التبويب" @@ -1037,10 +1043,10 @@ "message": "قائمة عناصر الهوية في صفحة التبويب لسهولة الملء التلقائي." }, "clickToAutofillOnVault": { - "message": "Click items to autofill on Vault view" + "message": "انقر فوق العناصر للملء التلقائي على عرض المخزن" }, "clickToAutofill": { - "message": "Click items in autofill suggestion to fill" + "message": "انقر فوق العناصر في اقتراح التعبئة التلقائية لملء" }, "clearClipboard": { "message": "مسح الحافظة", @@ -1056,50 +1062,76 @@ "notificationAddSave": { "message": "حفظ" }, - "loginSaveSuccessDetails": { - "message": "$USERNAME$ saved to Bitwarden.", + "notificationViewAria": { + "message": "View $ITEMNAME$, opens in new window", "placeholders": { - "username": { + "itemName": { "content": "$1" } }, - "description": "Shown to user after login is saved." + "description": "Aria label for the view button in notification bar confirmation message" }, - "loginUpdatedSuccessDetails": { - "message": "$USERNAME$ updated in Bitwarden.", + "notificationEditTooltip": { + "message": "Edit before saving", + "description": "Tooltip and Aria label for edit button on cipher item" + }, + "newNotification": { + "message": "New notification" + }, + "labelWithNotification": { + "message": "$LABEL$: New notification", + "description": "Label for the notification with a new login suggestion.", "placeholders": { - "username": { + "label": { + "content": "$1", + "example": "Login" + } + } + }, + "loginSaveConfirmation": { + "message": "$ITEMNAME$ saved to Bitwarden.", + "placeholders": { + "itemName": { "content": "$1" } }, - "description": "Shown to user after login is updated." + "description": "Shown to user after item is saved." + }, + "loginUpdatedConfirmation": { + "message": "$ITEMNAME$ updated in Bitwarden.", + "placeholders": { + "itemName": { + "content": "$1" + } + }, + "description": "Shown to user after item is updated." }, "saveAsNewLoginAction": { - "message": "Save as new login", + "message": "حفظ كتسجيل دخول جديد", "description": "Button text for saving login details as a new entry." }, "updateLoginAction": { - "message": "Update login", + "message": "تحديث تسجيل الدخول", "description": "Button text for updating an existing login entry." }, - "saveLoginPrompt": { - "message": "Save login?", + "saveLogin": { + "message": "Save login", "description": "Prompt asking the user if they want to save their login details." }, - "updateLoginPrompt": { - "message": "Update existing login?", + "updateLogin": { + "message": "Update existing login", "description": "Prompt asking the user if they want to update an existing login entry." }, "loginSaveSuccess": { - "message": "Login saved", + "message": "تم حفظ تسجيل الدخول", "description": "Message displayed when login details are successfully saved." }, "loginUpdateSuccess": { - "message": "Login updated", + "message": "تم تحديث تسجيل الدخول", "description": "Message displayed when login details are successfully updated." }, "loginUpdateTaskSuccess": { - "message": "Great job! You took the steps to make you and $ORGANIZATION$ more secure.", + "message": "عمل رائع! لقد اتخذت الخطوات لجعلك و $ORGANIZATION$ أكثر أمنا.", "placeholders": { "organization": { "content": "$1" @@ -1108,7 +1140,7 @@ "description": "Shown to user after login is updated." }, "loginUpdateTaskSuccessAdditional": { - "message": "Thank you for making $ORGANIZATION$ more secure. You have $TASK_COUNT$ more passwords to update.", + "message": "شكرا لك على جعل $ORGANIZATION$ أكثر أمنا. لديك $TASK_COUNT$ المزيد من كلمات المرور للتحديث.", "placeholders": { "organization": { "content": "$1" @@ -1120,15 +1152,15 @@ "description": "Shown to user after login is updated." }, "nextSecurityTaskAction": { - "message": "Change next password", + "message": "تغيير كلمة المرور التالية", "description": "Message prompting user to undertake completion of another security task." }, "saveFailure": { - "message": "Error saving", + "message": "خطأ في الحفظ", "description": "Error message shown when the system fails to save login details." }, "saveFailureDetails": { - "message": "Oh no! We couldn't save this. Try entering the details manually.", + "message": "أوه لا! لم نتمكن من حفظ هذا. حاول إدخال التفاصيل يدويا.", "description": "Detailed error message shown when saving login details fails." }, "enableChangedPasswordNotification": { @@ -1213,26 +1245,26 @@ "message": "ستُستخدم كلمة المرور هذه لتصدير واستيراد هذا المِلَفّ" }, "accountRestrictedOptionDescription": { - "message": "Use your account encryption key, derived from your account's username and Master Password, to encrypt the export and restrict import to only the current Bitwarden account." + "message": "استخدم مفتاح تشفير حسابك، مشتقة من اسم المستخدم في حسابك وكلمة المرور الرئيسية، لتشفير التصدير وتقييد الاستيراد إلى حساب بيتواردن الحالي فقط." }, "passwordProtectedOptionDescription": { - "message": "Set a file password to encrypt the export and import it to any Bitwarden account using the password for decryption." + "message": "تعيين كلمة مرور للملف لتشفير التصدير واستيراده إلى أي حساب بيتواردن باستخدام كلمة المرور لفك التشفير." }, "exportTypeHeading": { - "message": "Export type" + "message": "نوع التصدير" }, "accountRestricted": { - "message": "Account restricted" + "message": "الحساب مقيد" }, "filePasswordAndConfirmFilePasswordDoNotMatch": { - "message": "“File password” and “Confirm file password“ do not match." + "message": "\"كلمة مرور الملف\" و \"تأكيد كلمة مرور الملف\" غير متطابقين." }, "warning": { "message": "تحذير", "description": "WARNING (should stay in capitalized letters if the language permits)" }, "warningCapitalized": { - "message": "Warning", + "message": "تحذير", "description": "Warning (should maintain locale-relevant capitalization)" }, "confirmVaultExport": { @@ -1254,7 +1286,7 @@ "message": "مشترك" }, "bitwardenForBusinessPageDesc": { - "message": "Bitwarden for Business allows you to share your vault items with others by using an organization. Learn more on the bitwarden.com website." + "message": "بيتواردن للأعمال التجارية يسمح لك بمشاركة عناصر المخزن الخاصة بك مع الآخرين باستخدام منظمة. تعرف على المزيد على موقع bitwarden.com على شبكة الإنترنت." }, "moveToOrganization": { "message": "الانتقال إلى مؤسسة" @@ -1312,7 +1344,7 @@ "message": "الملف" }, "fileToShare": { - "message": "File to share" + "message": "ملف للمشاركة" }, "selectFile": { "message": "حدد ملفًا" @@ -1348,7 +1380,7 @@ "message": "1 جيغابايت وحدة تخزين مشفرة لمرفقات الملفات." }, "premiumSignUpEmergency": { - "message": "Emergency access." + "message": "الوصول الطارئ." }, "premiumSignUpTwoStepOptions": { "message": "خيارات تسجيل الدخول بخطوتين المملوكة لجهات اخرى مثل YubiKey و Duo." @@ -1369,7 +1401,7 @@ "message": "شراء العضوية المميزة" }, "premiumPurchaseAlertV2": { - "message": "You can purchase Premium from your account settings on the Bitwarden web app." + "message": "يمكنك شراء العضوية المميزة من إعدادات حسابك على تطبيق بيتواردن على شبكة الإنترنت." }, "premiumCurrentMember": { "message": "أنت عضو مميز!" @@ -1378,7 +1410,7 @@ "message": "شكرا لك على دعم Bitwarden." }, "premiumFeatures": { - "message": "Upgrade to Premium and receive:" + "message": "الترقية إلى النسخة الممتازة و استلام:" }, "premiumPrice": { "message": "الكل فقط بـ $PRICE$ /سنة!", @@ -1390,7 +1422,7 @@ } }, "premiumPriceV2": { - "message": "All for just $PRICE$ per year!", + "message": "كل شيء فقط $PRICE$ في السنة!", "placeholders": { "price": { "content": "$1", @@ -1417,10 +1449,10 @@ "message": "هذه المِيزة متاحة فقط للعضوية المميزة." }, "authenticationTimeout": { - "message": "Authentication timeout" + "message": "مهلة المصادقة" }, "authenticationSessionTimedOut": { - "message": "The authentication session timed out. Please restart the login process." + "message": "انتهت مهلة جلسة المصادقة. الرجاء إعادة تشغيل عملية تسجيل الدخول." }, "verificationCodeEmailSent": { "message": "تم إرسال رسالة التحقق إلى $EMAIL$.", @@ -1432,29 +1464,29 @@ } }, "dontAskAgainOnThisDeviceFor30Days": { - "message": "Don't ask again on this device for 30 days" + "message": "لا تسأل مرة أخرى على هذا الجهاز لمدة 30 يوماً" }, "selectAnotherMethod": { - "message": "Select another method", + "message": "اختر طريقة أخرى", "description": "Select another two-step login method" }, "useYourRecoveryCode": { - "message": "Use your recovery code" + "message": "استخدم رمز الاسترداد الخاص بك" }, "insertU2f": { "message": "أدخل مفتاح الأمان الخاص بك في منفذ USB كمبيوترك، إذا كان يحتوي على زر، إلمسه." }, "openInNewTab": { - "message": "Open in new tab" + "message": "فتح في علامة تبويب جديدة" }, "webAuthnAuthenticate": { "message": "مصادقة WebAuthn" }, "readSecurityKey": { - "message": "Read security key" + "message": "قراءة مفتاح الأمان" }, "awaitingSecurityKeyInteraction": { - "message": "Awaiting security key interaction..." + "message": "في انتظار التفاعل مع مفتاح الأمن..." }, "loginUnavailable": { "message": "تسجيل الدخول غير متاح" @@ -1469,7 +1501,7 @@ "message": "خيارات تسجيل الدخول بخطوتين" }, "selectTwoStepLoginMethod": { - "message": "Select two-step login method" + "message": "حدد طريقة تسجيل الدخول بخطوتين" }, "recoveryCodeDesc": { "message": "هل تفقد الوصول إلى جميع مزودي التحقق بعاملين؟ استخدم رمز الاسترداد الخاص بك لتعطيل جميع مزودي التحقق بعاملين من حسابك." @@ -1481,17 +1513,17 @@ "message": "تطبيق المصادقة" }, "authenticatorAppDescV2": { - "message": "Enter a code generated by an authenticator app like Bitwarden Authenticator.", + "message": "أدخل رمز تم إنشاؤه بواسطة تطبيق مصادقة مثل Bitwarden Authenticator.", "description": "'Bitwarden Authenticator' is a product name and should not be translated." }, "yubiKeyTitleV2": { - "message": "Yubico OTP Security Key" + "message": "مفتاح أمان OTP Yubico" }, "yubiKeyDesc": { "message": "استخدم YubiKey للوصول إلى حسابك. يعمل مع YubiKey 4 ،4 Nano ،4C، وأجهزة NEO." }, "duoDescV2": { - "message": "Enter a code generated by Duo Security.", + "message": "أدخل الرمز الذي تم إنشاؤه بواسطة نظام حماية الثنائي.", "description": "'Duo Security' and 'Duo Mobile' are product names and should not be translated." }, "duoOrganizationDesc": { @@ -1508,19 +1540,19 @@ "message": "البريد الإلكتروني" }, "emailDescV2": { - "message": "Enter a code sent to your email." + "message": "أدخل رمز مرسل إلى بريدك الإلكتروني." }, "selfHostedEnvironment": { "message": "البيئة المستضافة ذاتيا" }, "selfHostedBaseUrlHint": { - "message": "Specify the base URL of your on-premises hosted Bitwarden installation. Example: https://bitwarden.company.com" + "message": "حدد عنوان URL الأساسي لمبانيك التي استضافت تثبيت بتواردن على سبيل المثال: https://bitwarden.company.com" }, "selfHostedCustomEnvHeader": { - "message": "For advanced configuration, you can specify the base URL of each service independently." + "message": "للتكوين المتقدم، يمكنك تحديد عنوان URL الأساسي لكل خدمة بشكل مستقل." }, "selfHostedEnvFormInvalid": { - "message": "You must add either the base Server URL or at least one custom environment." + "message": "يجب عليك إضافة رابط الخادم الأساسي أو على الأقل بيئة مخصصة." }, "customEnvironment": { "message": "بيئة مخصصة" @@ -1529,7 +1561,7 @@ "message": "رابط الخادم" }, "selfHostBaseUrl": { - "message": "Self-host server URL", + "message": "رابط خادم الاستضافة الذاتية", "description": "Label for field requesting a self-hosted integration service URL" }, "apiUrl": { @@ -1555,22 +1587,22 @@ "description": "Represents the message for allowing the user to enable the autofill overlay" }, "autofillSuggestionsSectionTitle": { - "message": "Autofill suggestions" + "message": "اقتراحات التعبئة التلقائية" }, "showInlineMenuLabel": { - "message": "Show autofill suggestions on form fields" + "message": "إظهار اقتراحات التعبئة التلقائية في حقول النموذج" }, "showInlineMenuIdentitiesLabel": { - "message": "Display identities as suggestions" + "message": "عرض الهويات كاقتراحات" }, "showInlineMenuCardsLabel": { - "message": "Display cards as suggestions" + "message": "عرض البطاقات كاقتراحات" }, "showInlineMenuOnIconSelectionLabel": { - "message": "Display suggestions when icon is selected" + "message": "عرض الاقتراحات عند تحديد الأيقونة" }, "showInlineMenuOnFormFieldsDescAlt": { - "message": "Applies to all logged in accounts." + "message": "ينطبق على جميع الحسابات المسجل الدخول بها." }, "turnOffBrowserBuiltInPasswordManagerSettings": { "message": "إيقاف تشغيل إعدادات مدير كلمات المرور الافتراضي في متصفحك لتجنب التضارب." @@ -1591,7 +1623,7 @@ "description": "Overlay appearance select option for showing the field on click of the overlay icon" }, "enableAutoFillOnPageLoadSectionTitle": { - "message": "Autofill on page load" + "message": "التعبئة التلقائية عند تحميل الصفحة" }, "enableAutoFillOnPageLoad": { "message": "ملء تلقائي عند تحميل الصفحة" @@ -1603,7 +1635,7 @@ "message": "مواقع المساومة أو غير الموثوق بها يمكن أن تستغل الملء التلقائي في تحميل الصفحة." }, "learnMoreAboutAutofillOnPageLoadLinkText": { - "message": "Learn more about risks" + "message": "معرفة المزيد عن المخاطر" }, "learnMoreAboutAutofill": { "message": "تعرف على المزيد حول الملء التلقائي" @@ -1633,13 +1665,13 @@ "message": "فتح المخزن في الشريط الجانبي" }, "commandAutofillLoginDesc": { - "message": "Autofill the last used login for the current website" + "message": "ملء تلقائي لآخر تسجيل دخول مستخدم للموقع الحالي" }, "commandAutofillCardDesc": { - "message": "Autofill the last used card for the current website" + "message": "ملء تلقائي آخر بطاقة مستخدمة للموقع الحالي" }, "commandAutofillIdentityDesc": { - "message": "Autofill the last used identity for the current website" + "message": "ملء تلقائي آخر هوية مستخدمة للموقع الحالي" }, "commandGeneratePasswordDesc": { "message": "إنشاء واستنساخ كلمة مرور عشوائية جديدة إلى الحافظة" @@ -1663,7 +1695,7 @@ "message": "اسحب للفرز" }, "dragToReorder": { - "message": "Drag to reorder" + "message": "اسحب لإعادة الترتيب" }, "cfTypeText": { "message": "نص" @@ -1675,7 +1707,7 @@ "message": "قيمة منطقية" }, "cfTypeCheckbox": { - "message": "Checkbox" + "message": "خانة" }, "cfTypeLinked": { "message": "مرتبط", @@ -1860,10 +1892,10 @@ "message": "الهوية" }, "typeSshKey": { - "message": "SSH key" + "message": "مفتاح SSH" }, "newItemHeader": { - "message": "New $TYPE$", + "message": "جديد $TYPE$", "placeholders": { "type": { "content": "$1", @@ -1872,7 +1904,7 @@ } }, "editItemHeader": { - "message": "Edit $TYPE$", + "message": "تحرير $TYPE$", "placeholders": { "type": { "content": "$1", @@ -1881,7 +1913,7 @@ } }, "viewItemHeader": { - "message": "View $TYPE$", + "message": "عرض $TYPE$", "placeholders": { "type": { "content": "$1", @@ -1893,13 +1925,13 @@ "message": "سجل كلمة المرور" }, "generatorHistory": { - "message": "Generator history" + "message": "تاريخ المولدات" }, "clearGeneratorHistoryTitle": { - "message": "Clear generator history" + "message": "مسح سجل المولدات" }, "cleargGeneratorHistoryDescription": { - "message": "If you continue, all entries will be permanently deleted from generator's history. Are you sure you want to continue?" + "message": "إذا قمت بالمتابعة، سيتم حذف جميع الإدخالات بشكل دائم من سجل المولد. هل أنت متأكد من أنك تريد المتابعة؟" }, "back": { "message": "رجوع" @@ -1908,7 +1940,7 @@ "message": "المجموعات" }, "nCollections": { - "message": "$COUNT$ collections", + "message": "مجموعات $COUNT$", "placeholders": { "count": { "content": "$1", @@ -1938,7 +1970,7 @@ "message": "ملاحظات آمنة" }, "sshKeys": { - "message": "SSH Keys" + "message": "مفاتيح SSH" }, "clear": { "message": "مسح", @@ -1964,7 +1996,7 @@ "description": "Domain name. Ex. website.com" }, "baseDomainOptionRecommended": { - "message": "Base domain (recommended)", + "message": "النطاق الأساسي (مستحسن)", "description": "Domain name. Ex. website.com" }, "domainName": { @@ -2018,7 +2050,7 @@ "message": "لا توجد كلمات مرور للعرض." }, "clearHistory": { - "message": "Clear history" + "message": "مسح المحفوظات" }, "nothingToShow": { "message": "لا يوجد شيء لعرضه" @@ -2139,7 +2171,7 @@ "message": "مولد اسم المستخدم" }, "useThisEmail": { - "message": "Use this email" + "message": "استخدم هذا البريد الإلكتروني" }, "useThisPassword": { "message": "استخدم كلمة المرور هذه" @@ -2159,22 +2191,22 @@ "description": "This will be used as part of a larger sentence, broken up to include the generator icon. The full sentence will read 'Use the generator [GENERATOR_ICON] to create a strong unique password'" }, "vaultCustomization": { - "message": "Vault customization" + "message": "تخصيص المخزن" }, "vaultTimeoutAction": { "message": "إجراء مهلة المخزن" }, "vaultTimeoutAction1": { - "message": "Timeout action" + "message": "إجراء المهلة" }, "newCustomizationOptionsCalloutTitle": { - "message": "New customization options" + "message": "خيارات التخصيص الجديدة" }, "newCustomizationOptionsCalloutContent": { - "message": "Customize your vault experience with quick copy actions, compact mode, and more!" + "message": "تخصيص تجربة المخزن الخاص بك مع إجراءات النسخ السريعة، والوضع المدمج، والمزيد!" }, "newCustomizationOptionsCalloutLink": { - "message": "View all Appearance settings" + "message": "عرض جميع إعدادات المظهر" }, "lock": { "message": "قفل", @@ -2323,7 +2355,7 @@ "message": "سياسة الخصوصية" }, "yourNewPasswordCannotBeTheSameAsYourCurrentPassword": { - "message": "Your new password cannot be the same as your current password." + "message": "كلمة المرور الجديدة لا يمكن أن تكون نفس كلمة المرور الحالية." }, "hintEqualsPassword": { "message": "لا يمكن أن يكون تلميح كلمة المرور نفس كلمة المرور الخاصة بك." @@ -2332,10 +2364,10 @@ "message": "موافق" }, "errorRefreshingAccessToken": { - "message": "Access Token Refresh Error" + "message": "خطأ في تحديث رمز الوصول" }, "errorRefreshingAccessTokenDesc": { - "message": "No refresh token or API keys found. Please try logging out and logging back in." + "message": "لم يتم العثور على رمز تحديث أو مفاتيح API. الرجاء محاولة تسجيل الخروج وتسجيل الدخول مرة أخرى." }, "desktopSyncVerificationTitle": { "message": "التحقق من مزامنة سطح المكتب" @@ -2377,7 +2409,7 @@ "message": "عدم تطابق المفتاح الحيوي" }, "nativeMessagingWrongUserKeyDesc": { - "message": "Biometric unlock failed. The biometric secret key failed to unlock the vault. Please try to set up biometrics again." + "message": "فشل فتح القفل البيومتري. فشل المفتاح السري الحيوي في فتح خزانة. الرجاء محاولة إعداد القياسات الحيوية مرة أخرى." }, "biometricsNotEnabledTitle": { "message": "لم يتم إعداد القياسات الحيوية" @@ -2392,16 +2424,16 @@ "message": "القياسات الحيوية للمتصفح غير مدعومة على هذا الجهاز." }, "biometricsNotUnlockedTitle": { - "message": "User locked or logged out" + "message": "المستخدم مقفل أو مسجل الخروج" }, "biometricsNotUnlockedDesc": { - "message": "Please unlock this user in the desktop application and try again." + "message": "الرجاء فتح هذا المستخدم في تطبيق سطح المكتب وحاول مرة أخرى." }, "biometricsNotAvailableTitle": { - "message": "Biometric unlock unavailable" + "message": "فتح القفل الحيوي غير متوفر" }, "biometricsNotAvailableDesc": { - "message": "Biometric unlock is currently unavailable. Please try again later." + "message": "الفتح الحيوي غير متوفر حاليا. الرجاء المحاولة مرة أخرى لاحقاً." }, "biometricsFailedTitle": { "message": "فشل القياسات الحيوية" @@ -2425,20 +2457,20 @@ "message": "بسبب سياسة المؤسسة، يمنع عليك حفظ العناصر في خزانتك الشخصية. غيّر خيار الملكية إلى مؤسسة واختر من المجموعات المتاحة." }, "personalOwnershipPolicyInEffect": { - "message": "An organization policy is affecting your ownership options." + "message": "تؤثر سياسة مؤسسة على خيارات الملكية الخاصة بك." }, "personalOwnershipPolicyInEffectImports": { - "message": "An organization policy has blocked importing items into your individual vault." + "message": "لقد حالت سياسة المؤسسة دون استيراد العناصر إلى خزانتك الشخصية." }, "domainsTitle": { "message": "النطاقات", "description": "A category title describing the concept of web domains" }, "blockedDomains": { - "message": "Blocked domains" + "message": "النطاقات المحظورة" }, "learnMoreAboutBlockedDomains": { - "message": "Learn more about blocked domains" + "message": "معرفة المزيد حول النطاقات المحظورة" }, "excludedDomains": { "message": "النطاقات المستبعدة" @@ -2450,19 +2482,19 @@ "message": "Bitwarden لن يطلب حفظ تفاصيل تسجيل الدخول لهذه النطافات لجميع الحسابات مسجلة الدخول. يجب عليك تحديث الصفحة لكي تصبح التغييرات نافذة المفعول." }, "blockedDomainsDesc": { - "message": "Autofill and other related features will not be offered for these websites. You must refresh the page for changes to take effect." + "message": "لن يتم توفير الملء التلقائي والمميزات الأخرى ذات الصلة لهذه المواقع. يجب عليك تحديث الصفحة لكي تصبح التغييرات نافذة المفعول." }, "autofillBlockedNoticeV2": { - "message": "Autofill is blocked for this website." + "message": "التعبئة التلقائية محظورة لهذا الموقع." }, "autofillBlockedNoticeGuidance": { - "message": "Change this in settings" + "message": "تغيير هذا في الإعدادات" }, "change": { - "message": "Change" + "message": "تغيير" }, "changeButtonTitle": { - "message": "Change password - $ITEMNAME$", + "message": "تغيير كلمة المرور - $ITEMNAME$", "placeholders": { "itemname": { "content": "$1", @@ -2715,27 +2747,27 @@ "message": "كلمة المرور الجديدة" }, "sendDisabled": { - "message": "Send removed", + "message": "تم إزالة الإرسال", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "sendDisabledWarning": { - "message": "Due to an enterprise policy, you are only able to delete an existing Send.", + "message": "بسبب سياسة المؤسسة، يمكنك فقط حذف إرسال موجود بالفعل.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "createdSend": { - "message": "Send created", + "message": "تم إنشاء إرسال جديد", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "createdSendSuccessfully": { - "message": "Send created successfully!", + "message": "تم إنشاء الإرسال بنجاح!", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "sendExpiresInHoursSingle": { - "message": "The Send will be available to anyone with the link for the next 1 hour.", + "message": "سيكون الإرسال متاحا لأي شخص لديه الرابط لمدة ساعة واحدة قادمة.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "sendExpiresInHours": { - "message": "The Send will be available to anyone with the link for the next $HOURS$ hours.", + "message": "سيكون الإرسال متاحا لأي شخص لديه الرابط لساعات $HOURS$ القادمة.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated.", "placeholders": { "hours": { @@ -2745,11 +2777,11 @@ } }, "sendExpiresInDaysSingle": { - "message": "The Send will be available to anyone with the link for the next 1 day.", + "message": "سيكون الإرسال متاحا لأي شخص لديه الرابط لليوم التالي.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "sendExpiresInDays": { - "message": "The Send will be available to anyone with the link for the next $DAYS$ days.", + "message": "سيكون الإرسال متاحا لأي شخص لديه الرابط خلال الأيام $DAYS$ القادمة.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated.", "placeholders": { "days": { @@ -2759,32 +2791,32 @@ } }, "sendLinkCopied": { - "message": "Send link copied", + "message": "إرسال رابط منسوخ", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "editedSend": { - "message": "Send saved", + "message": "إرسال محفوظ", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "sendFilePopoutDialogText": { - "message": "Pop out extension?", + "message": "تمديد مستخرج؟", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "sendFilePopoutDialogDesc": { - "message": "To create a file Send, you need to pop out the extension to a new window.", + "message": "لإنشاء إرسال ملف، تحتاج إلى تثبيت الملحق إلى نافذة جديدة.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "sendLinuxChromiumFileWarning": { - "message": "In order to choose a file, open the extension in the sidebar (if possible) or pop out to a new window by clicking this banner." + "message": "من أجل اختيار ملف، قم بفتح الملحق في الشريط الجانبي (إن أمكن) أو الإنتقال إلى نافذة جديدة بالنقر على هذا الشعار." }, "sendFirefoxFileWarning": { - "message": "In order to choose a file using Firefox, open the extension in the sidebar or pop out to a new window by clicking this banner." + "message": "من أجل اختيار ملف باستخدام فايرفوكس، افتح الملحق في الشريط الجانبي أو اخرج إلى نافذة جديدة من خلال النقر على هذا الشعار." }, "sendSafariFileWarning": { - "message": "In order to choose a file using Safari, pop out to a new window by clicking this banner." + "message": "من أجل اختيار ملف باستخدام سافاري، انتقل إلى نافذة جديدة بالنقر على هذا الشعار." }, "popOut": { - "message": "Pop out" + "message": "انبثق" }, "sendFileCalloutHeader": { "message": "قبل أن تبدأ" @@ -2796,19 +2828,19 @@ "message": "صلاحية تاريخ الحذف المقدّم غير صحيح." }, "expirationDateAndTimeRequired": { - "message": "An expiration date and time are required." + "message": "مطلوب تاريخ ووقت انتهاء الصلاحية." }, "deletionDateAndTimeRequired": { - "message": "A deletion date and time are required." + "message": "مطلوب تاريخ ووقت الحذف." }, "dateParsingError": { - "message": "There was an error saving your deletion and expiration dates." + "message": "حدث خطأ أثناء حفظ تواريخ الحذف وانتهاء الصلاحية." }, "hideYourEmail": { - "message": "Hide your email address from viewers." + "message": "إخفاء عنوان البريد الإلكتروني الخاص بك من المشاهدين." }, "passwordPrompt": { - "message": "Master password re-prompt" + "message": "إعادة توجيه كلمة المرور الرئيسية" }, "passwordConfirmation": { "message": "تأكيد كلمة المرور الرئيسية" @@ -2820,13 +2852,13 @@ "message": "تأكيد البريد الإلكتروني مطلوب" }, "emailVerifiedV2": { - "message": "Email verified" + "message": "تم التحقق من البريد الإلكتروني" }, "emailVerificationRequiredDesc": { "message": "يجب عليك تأكيد بريدك الإلكتروني لاستخدام هذه الميزة. يمكنك تأكيد بريدك الإلكتروني في خزانة الويب." }, "updatedMasterPassword": { - "message": "Updated master password" + "message": "تحديث كلمة المرور الرئيسية" }, "updateMasterPassword": { "message": "تحديث كلمة المرور الرئيسية" @@ -2835,10 +2867,10 @@ "message": "تم تغيير كلمة المرور الرئيسية الخاصة بك مؤخرًا من قبل مسؤول في مؤسستك. من أجل الوصول إلى الخزانة، يجب عليك تحديثها الآن. سيتم تسجيل خروجك من الجلسة الحالية، مما يتطلب منك تسجيل الدخول مرة أخرى. قد تظل الجلسات النشطة على أجهزة أخرى نشطة لمدة تصل إلى ساعة واحدة." }, "updateWeakMasterPasswordWarning": { - "message": "Your master password does not meet one or more of your organization policies. In order to access the vault, you must update your master password now. Proceeding will log you out of your current session, requiring you to log back in. Active sessions on other devices may continue to remain active for up to one hour." + "message": "كلمة المرور الرئيسية الخاصة بك لا تفي بواحدة أو أكثر من سياسات مؤسستك. من أجل الوصول إلى الخزنة، يجب عليك تحديث كلمة المرور الرئيسية الآن. سيتم تسجيل خروجك من الجلسة الحالية، مما يتطلب منك تسجيل الدخول مرة أخرى. وقد تظل الجلسات النشطة على أجهزة أخرى نشطة لمدة تصل إلى ساعة واحدة." }, "tdeDisabledMasterPasswordRequired": { - "message": "Your organization has disabled trusted device encryption. Please set a master password to access your vault." + "message": "لقد قامت مؤسستك بتعطيل تشفير الجهاز الموثوق به. الرجاء تعيين كلمة مرور رئيسية للوصول إلى خزانك." }, "resetPasswordPolicyAutoEnroll": { "message": "التسجيل التلقائي" @@ -2847,22 +2879,22 @@ "message": "هذه المؤسسة لديها سياسة تقوم تلقائياً بتسجيلك في إعادة تعيين كلمة المرور. هذا التسجيل سيسمح لمسؤولي المؤسسة بتغيير كلمة المرور الرئيسية الخاصة بك." }, "selectFolder": { - "message": "Select folder..." + "message": "حدد المجلد..." }, "noFoldersFound": { - "message": "No folders found", + "message": "لم يتم العثور على أي مجلدات", "description": "Used as a message within the notification bar when no folders are found" }, "orgPermissionsUpdatedMustSetPassword": { - "message": "Your organization permissions were updated, requiring you to set a master password.", + "message": "تم تحديث أذونات مؤسستك، مما يتطلب عليك تعيين كلمة مرور رئيسية.", "description": "Used as a card title description on the set password page to explain why the user is there" }, "orgRequiresYouToSetPassword": { - "message": "Your organization requires you to set a master password.", + "message": "تتطلب مؤسستك تعيين كلمة مرور رئيسية.", "description": "Used as a card title description on the set password page to explain why the user is there" }, "cardMetrics": { - "message": "out of $TOTAL$", + "message": "من $TOTAL$", "placeholders": { "total": { "content": "$1", @@ -2881,7 +2913,7 @@ "message": "دقائق" }, "vaultTimeoutPolicyAffectingOptions": { - "message": "Enterprise policy requirements have been applied to your timeout options" + "message": "تم تطبيق متطلبات سياسة المؤسسة على خيارات المهلة الخاصة بك" }, "vaultTimeoutPolicyInEffect": { "message": "سياسات مؤسستك تؤثر على مهلة الخزانة الخاص بك. الحد الأقصى المسموح به لمهلة الخزانة هو $HOURS$ ساعة/ساعات و $MINUTES$ دقيقة/دقائق.", @@ -2897,7 +2929,7 @@ } }, "vaultTimeoutPolicyInEffect1": { - "message": "$HOURS$ hour(s) and $MINUTES$ minute(s) maximum.", + "message": "$HOURS$ ساعة(ساعات) و $MINUTES$ دقيقة(دقائق) كحد أقصى.", "placeholders": { "hours": { "content": "$1", @@ -2910,7 +2942,7 @@ } }, "vaultTimeoutPolicyMaximumError": { - "message": "Timeout exceeds the restriction set by your organization: $HOURS$ hour(s) and $MINUTES$ minute(s) maximum", + "message": "تجاوز المهلة القيد الذي تضعه مؤسستك: $HOURS$ ساعة(ساعات) و $MINUTES$ دقيقة(دقائق) كحد أقصى", "placeholders": { "hours": { "content": "$1", @@ -2923,7 +2955,7 @@ } }, "vaultTimeoutPolicyWithActionInEffect": { - "message": "Your organization policies are affecting your vault timeout. Maximum allowed vault timeout is $HOURS$ hour(s) and $MINUTES$ minute(s). Your vault timeout action is set to $ACTION$.", + "message": "سياسات مؤسستك تؤثر على مهلة خزانتك. الحد الأقصى المسموح به لمهلة الخزانة هو $HOURS$ ساعة(ساعات) و $MINUTES$ دقيقة(دقائق). يتم تعيين إجراء مهلة المخزن الخاص بك إلى $ACTION$.", "placeholders": { "hours": { "content": "$1", @@ -2940,7 +2972,7 @@ } }, "vaultTimeoutActionPolicyInEffect": { - "message": "Your organization policies have set your vault timeout action to $ACTION$.", + "message": "لقد حددت سياسات مؤسستك إجراء مهلة المخزن الخاص بك إلى $ACTION$.", "placeholders": { "action": { "content": "$1", @@ -2964,7 +2996,7 @@ "message": "لم يتم العثور على معرف فريد." }, "convertOrganizationEncryptionDesc": { - "message": "$ORGANIZATION$ is using SSO with a self-hosted key server. A master password is no longer required to log in for members of this organization.", + "message": "يستخدم $ORGANIZATION$ SSO مع خادم مفتاح الاستضافة الذاتية. لم تعد هناك حاجة إلى كلمة مرور رئيسية لتسجيل الدخول لأعضاء هذه المنظمة.", "placeholders": { "organization": { "content": "$1", @@ -2988,7 +3020,7 @@ "message": "لقد غادرت المؤسسة." }, "toggleCharacterCount": { - "message": "Toggle character count" + "message": "تبديل عدد الأحرف" }, "sessionTimeout": { "message": "انتهت مدة جلستك. يرجى العودة ومحاولة تسجيل الدخول مرة أخرى." @@ -2997,7 +3029,7 @@ "message": "جاري تصدير الخزانة الشخصية" }, "exportingIndividualVaultDescription": { - "message": "Only the individual vault items associated with $EMAIL$ will be exported. Organization vault items will not be included. Only vault item information will be exported and will not include associated attachments.", + "message": "سيتم تصدير عناصر المخزن الفردية المرتبطة بـ $EMAIL$ فقط. لن يتم إدراج عناصر مخزن المنظمة. سيتم تصدير معلومات المنتج فقط ولن تشمل المرفقات المرتبطة بها.", "placeholders": { "email": { "content": "$1", @@ -3006,7 +3038,7 @@ } }, "exportingIndividualVaultWithAttachmentsDescription": { - "message": "Only the individual vault items including attachments associated with $EMAIL$ will be exported. Organization vault items will not be included", + "message": "فقط عناصر المخزن الفردية بما في ذلك المرفقات المرتبطة بـ $EMAIL$ سيتم تصديرها. لن يتم تضمين عناصر مخزن المنظمة", "placeholders": { "email": { "content": "$1", @@ -3015,10 +3047,10 @@ } }, "exportingOrganizationVaultTitle": { - "message": "Exporting organization vault" + "message": "خزانة منظمة التصدير" }, "exportingOrganizationVaultDesc": { - "message": "Only the organization vault associated with $ORGANIZATION$ will be exported. Items in individual vaults or other organizations will not be included.", + "message": "فقط مستودع المنظمة المرتبط بـ $ORGANIZATION$ سيتم تصديره. لن يتم إدراج العناصر في المستودعات الفردية أو المنظمات الأخرى.", "placeholders": { "organization": { "content": "$1", @@ -3030,27 +3062,27 @@ "message": "خطأ" }, "decryptionError": { - "message": "Decryption error" + "message": "خطأ فك التشفير" }, "couldNotDecryptVaultItemsBelow": { - "message": "Bitwarden could not decrypt the vault item(s) listed below." + "message": "تعذر على بتواردن فك تشفير العنصر (العناصر) المدرجة أدناه." }, "contactCSToAvoidDataLossPart1": { - "message": "Contact customer success", + "message": "تم الاتصال بالعميل بنجاح", "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" }, "contactCSToAvoidDataLossPart2": { - "message": "to avoid additional data loss.", + "message": "لتجنب فقدان بيانات إضافية.", "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" }, "generateUsername": { "message": "إنشاء اسم المستخدم" }, "generateEmail": { - "message": "Generate email" + "message": "إنشاء بريد إلكتروني" }, "spinboxBoundariesHint": { - "message": "Value must be between $MIN$ and $MAX$.", + "message": "يجب أن تكون القيمة بين $MIN$ و $MAX$.", "description": "Explains spin box minimum and maximum values to the user", "placeholders": { "min": { @@ -3064,7 +3096,7 @@ } }, "passwordLengthRecommendationHint": { - "message": " Use $RECOMMENDED$ characters or more to generate a strong password.", + "message": " استخدم أحرف $RECOMMENDED$ أو أكثر لإنشاء كلمة مرور قوية.", "description": "Appended to `spinboxBoundariesHint` to recommend a length to the user. This must include any language-specific 'sentence' separator characters (e.g. a space in english).", "placeholders": { "recommended": { @@ -3074,7 +3106,7 @@ } }, "passphraseNumWordsRecommendationHint": { - "message": " Use $RECOMMENDED$ words or more to generate a strong passphrase.", + "message": " استخدم كلمات $RECOMMENDED$ أو أكثر لإنشاء عبارة مرور قوية.", "description": "Appended to `spinboxBoundariesHint` to recommend a number of words to the user. This must include any language-specific 'sentence' separator characters (e.g. a space in english).", "placeholders": { "recommended": { @@ -3084,7 +3116,7 @@ } }, "plusAddressedEmail": { - "message": "Plus addressed email", + "message": "بريد إلكتروني إضافي", "description": "Username generator option that appends a random sub-address to the username. For example: address+subaddress@email.com" }, "plusAddressedEmailDesc": { @@ -4928,8 +4960,8 @@ "message": "Password regenerated", "description": "Notification message for when a password has been regenerated" }, - "saveLoginToBitwarden": { - "message": "Save login to Bitwarden?", + "saveToBitwarden": { + "message": "Save to Bitwarden", "description": "Confirmation message for saving a login to Bitwarden" }, "spaceCharacterDescriptor": { @@ -5169,6 +5201,12 @@ "changeAtRiskPassword": { "message": "Change at-risk password" }, + "settingsVaultOptions": { + "message": "Vault options" + }, + "emptyVaultDescription": { + "message": "The vault protects more than just your passwords. Store secure logins, IDs, cards and notes securely here." + }, "introCarouselLabel": { "message": "Welcome to Bitwarden" }, @@ -5195,5 +5233,50 @@ }, "secureDevicesBody": { "message": "Save unlimited passwords across unlimited devices with Bitwarden mobile, browser, and desktop apps." + }, + "emptyVaultNudgeTitle": { + "message": "Import existing passwords" + }, + "emptyVaultNudgeBody": { + "message": "Use the importer to quickly transfer logins to Bitwarden without manually adding them." + }, + "emptyVaultNudgeButton": { + "message": "Import now" + }, + "hasItemsVaultNudgeTitle": { + "message": "Welcome to your vault!" + }, + "hasItemsVaultNudgeBody": { + "message": "Autofill items for the current page\nFavorite items for easy access\nSearch your vault for something else" + }, + "newLoginNudgeTitle": { + "message": "Save time with autofill" + }, + "newLoginNudgeBody": { + "message": "Include a Website so this login appears as an autofill suggestion." + }, + "newCardNudgeTitle": { + "message": "Seamless online checkout" + }, + "newCardNudgeBody": { + "message": "With cards, easily autofill payment forms securely and accurately." + }, + "newIdentityNudgeTitle": { + "message": "Simplify creating accounts" + }, + "newIdentityNudgeBody": { + "message": "With identities, quickly autofill long registration or contact forms." + }, + "newNoteNudgeTitle": { + "message": "Keep your sensitive data safe" + }, + "newNoteNudgeBody": { + "message": "With notes, securely store sensitive data like banking or insurance details." + }, + "newSshNudgeTitle": { + "message": "Developer-friendly SSH access" + }, + "newSshNudgeBody": { + "message": "Store your keys and connect with the SSH agent for fast, encrypted authentication." } } diff --git a/apps/browser/src/_locales/az/messages.json b/apps/browser/src/_locales/az/messages.json index 41e6d9b5ab9..3550ec52462 100644 --- a/apps/browser/src/_locales/az/messages.json +++ b/apps/browser/src/_locales/az/messages.json @@ -2,6 +2,9 @@ "appName": { "message": "Bitwarden" }, + "appLogoLabel": { + "message": "Bitwarden loqosu" + }, "extName": { "message": "Bitwarden Parol Meneceri", "description": "Extension name, MUST be less than 40 characters (Safari restriction)" @@ -883,6 +886,9 @@ "followTheStepsBelowToFinishLoggingIn": { "message": "Giriş prosesini tamamlamaq üçün aşağıdakı addımları izləyin." }, + "followTheStepsBelowToFinishLoggingInWithSecurityKey": { + "message": "Güvənlik açarınızla girişi tamamlamaq üçün aşağıdakı addımları izləyin." + }, "restartRegistration": { "message": "Qeydiyyatı yenidən başlat" }, @@ -1056,23 +1062,49 @@ "notificationAddSave": { "message": "Saxla" }, - "loginSaveSuccessDetails": { - "message": "$USERNAME$ Bitwarden-də saxlanıldı.", + "notificationViewAria": { + "message": "$ITEMNAME$ bax, yeni pəncərədə açılır", "placeholders": { - "username": { + "itemName": { "content": "$1" } }, - "description": "Shown to user after login is saved." + "description": "Aria label for the view button in notification bar confirmation message" }, - "loginUpdatedSuccessDetails": { - "message": "$USERNAME$ Bitwarden-də güncəlləndi.", + "notificationEditTooltip": { + "message": "Saxlamazdan əvvəl düzəliş et", + "description": "Tooltip and Aria label for edit button on cipher item" + }, + "newNotification": { + "message": "Yeni bildiriş" + }, + "labelWithNotification": { + "message": "$LABEL$: Yeni bildiriş", + "description": "Label for the notification with a new login suggestion.", "placeholders": { - "username": { + "label": { + "content": "$1", + "example": "Login" + } + } + }, + "loginSaveConfirmation": { + "message": "$ITEMNAME$ Bitwarden-də saxlanıldı.", + "placeholders": { + "itemName": { "content": "$1" } }, - "description": "Shown to user after login is updated." + "description": "Shown to user after item is saved." + }, + "loginUpdatedConfirmation": { + "message": "$ITEMNAME$ Bitwarden-də güncəlləndi.", + "placeholders": { + "itemName": { + "content": "$1" + } + }, + "description": "Shown to user after item is updated." }, "saveAsNewLoginAction": { "message": "Yeni giriş kimi saxla", @@ -1082,12 +1114,12 @@ "message": "Giriş məlumatlarını güncəllə", "description": "Button text for updating an existing login entry." }, - "saveLoginPrompt": { - "message": "Giriş məlumatları saxlanılsın?", + "saveLogin": { + "message": "Girişi saxla", "description": "Prompt asking the user if they want to save their login details." }, - "updateLoginPrompt": { - "message": "Mövcud giriş məlumatları güncəllənsin?", + "updateLogin": { + "message": "Mövcud giriş məlumatlarını güncəllə", "description": "Prompt asking the user if they want to update an existing login entry." }, "loginSaveSuccess": { @@ -1099,7 +1131,7 @@ "description": "Message displayed when login details are successfully updated." }, "loginUpdateTaskSuccess": { - "message": "Great job! You took the steps to make you and $ORGANIZATION$ more secure.", + "message": "Əhsən sizə! Özünüzü və $ORGANIZATION$ təşkilatını daha güvənli etmək üçün addımlar atdınız.", "placeholders": { "organization": { "content": "$1" @@ -1108,7 +1140,7 @@ "description": "Shown to user after login is updated." }, "loginUpdateTaskSuccessAdditional": { - "message": "Thank you for making $ORGANIZATION$ more secure. You have $TASK_COUNT$ more passwords to update.", + "message": "$ORGANIZATION$ təşkilatını daha güvənli hala gətirdiyiniz üçün təşəkkürlər. Daha $TASK_COUNT$ parolunuz güncəllənməlidir.", "placeholders": { "organization": { "content": "$1" @@ -1120,7 +1152,7 @@ "description": "Shown to user after login is updated." }, "nextSecurityTaskAction": { - "message": "Change next password", + "message": "Növbəti parolu dəyişdir", "description": "Message prompting user to undertake completion of another security task." }, "saveFailure": { @@ -3259,7 +3291,7 @@ "message": "API açar" }, "ssoKeyConnectorError": { - "message": "Açar bağlayıcı xətası: Açar Bağlayıcının mövcud olduğuna və düzgün işlədiyinə əmin olun." + "message": "Key connector xətası: \"Key connector\"un mövcud olduğuna və düzgün işlədiyinə əmin olun." }, "premiumSubcriptionRequired": { "message": "Premium abunəlik tələb olunur" @@ -4928,8 +4960,8 @@ "message": "Parol yenidən yaradıldı", "description": "Notification message for when a password has been regenerated" }, - "saveLoginToBitwarden": { - "message": "Giriş Bitwarden-də saxlanılsın?", + "saveToBitwarden": { + "message": "\"Bitwarden\"də saxla", "description": "Confirmation message for saving a login to Bitwarden" }, "spaceCharacterDescriptor": { @@ -5169,6 +5201,12 @@ "changeAtRiskPassword": { "message": "Riskli parolları dəyişdir" }, + "settingsVaultOptions": { + "message": "Vault options" + }, + "emptyVaultDescription": { + "message": "The vault protects more than just your passwords. Store secure logins, IDs, cards and notes securely here." + }, "introCarouselLabel": { "message": "Bitwarden-ə xoş gəlmisiniz" }, @@ -5195,5 +5233,50 @@ }, "secureDevicesBody": { "message": "Bitwarden mobil, brauzer və masaüstü tətbiqləri ilə limitsiz cihaz arasında limitsiz parol saxlayın." + }, + "emptyVaultNudgeTitle": { + "message": "Import existing passwords" + }, + "emptyVaultNudgeBody": { + "message": "Use the importer to quickly transfer logins to Bitwarden without manually adding them." + }, + "emptyVaultNudgeButton": { + "message": "Import now" + }, + "hasItemsVaultNudgeTitle": { + "message": "Welcome to your vault!" + }, + "hasItemsVaultNudgeBody": { + "message": "Autofill items for the current page\nFavorite items for easy access\nSearch your vault for something else" + }, + "newLoginNudgeTitle": { + "message": "Save time with autofill" + }, + "newLoginNudgeBody": { + "message": "Include a Website so this login appears as an autofill suggestion." + }, + "newCardNudgeTitle": { + "message": "Seamless online checkout" + }, + "newCardNudgeBody": { + "message": "With cards, easily autofill payment forms securely and accurately." + }, + "newIdentityNudgeTitle": { + "message": "Simplify creating accounts" + }, + "newIdentityNudgeBody": { + "message": "With identities, quickly autofill long registration or contact forms." + }, + "newNoteNudgeTitle": { + "message": "Keep your sensitive data safe" + }, + "newNoteNudgeBody": { + "message": "With notes, securely store sensitive data like banking or insurance details." + }, + "newSshNudgeTitle": { + "message": "Developer-friendly SSH access" + }, + "newSshNudgeBody": { + "message": "Store your keys and connect with the SSH agent for fast, encrypted authentication." } } diff --git a/apps/browser/src/_locales/be/messages.json b/apps/browser/src/_locales/be/messages.json index f828400c575..20c5bb8ff35 100644 --- a/apps/browser/src/_locales/be/messages.json +++ b/apps/browser/src/_locales/be/messages.json @@ -2,6 +2,9 @@ "appName": { "message": "Bitwarden" }, + "appLogoLabel": { + "message": "Bitwarden logo" + }, "extName": { "message": "Менеджар пароляў Bitwarden", "description": "Extension name, MUST be less than 40 characters (Safari restriction)" @@ -883,6 +886,9 @@ "followTheStepsBelowToFinishLoggingIn": { "message": "Follow the steps below to finish logging in." }, + "followTheStepsBelowToFinishLoggingInWithSecurityKey": { + "message": "Follow the steps below to finish logging in with your security key." + }, "restartRegistration": { "message": "Restart registration" }, @@ -1056,23 +1062,49 @@ "notificationAddSave": { "message": "Захаваць" }, - "loginSaveSuccessDetails": { - "message": "$USERNAME$ saved to Bitwarden.", + "notificationViewAria": { + "message": "View $ITEMNAME$, opens in new window", "placeholders": { - "username": { + "itemName": { "content": "$1" } }, - "description": "Shown to user after login is saved." + "description": "Aria label for the view button in notification bar confirmation message" }, - "loginUpdatedSuccessDetails": { - "message": "$USERNAME$ updated in Bitwarden.", + "notificationEditTooltip": { + "message": "Edit before saving", + "description": "Tooltip and Aria label for edit button on cipher item" + }, + "newNotification": { + "message": "New notification" + }, + "labelWithNotification": { + "message": "$LABEL$: New notification", + "description": "Label for the notification with a new login suggestion.", "placeholders": { - "username": { + "label": { + "content": "$1", + "example": "Login" + } + } + }, + "loginSaveConfirmation": { + "message": "$ITEMNAME$ saved to Bitwarden.", + "placeholders": { + "itemName": { "content": "$1" } }, - "description": "Shown to user after login is updated." + "description": "Shown to user after item is saved." + }, + "loginUpdatedConfirmation": { + "message": "$ITEMNAME$ updated in Bitwarden.", + "placeholders": { + "itemName": { + "content": "$1" + } + }, + "description": "Shown to user after item is updated." }, "saveAsNewLoginAction": { "message": "Save as new login", @@ -1082,12 +1114,12 @@ "message": "Update login", "description": "Button text for updating an existing login entry." }, - "saveLoginPrompt": { - "message": "Save login?", + "saveLogin": { + "message": "Save login", "description": "Prompt asking the user if they want to save their login details." }, - "updateLoginPrompt": { - "message": "Update existing login?", + "updateLogin": { + "message": "Update existing login", "description": "Prompt asking the user if they want to update an existing login entry." }, "loginSaveSuccess": { @@ -4928,8 +4960,8 @@ "message": "Password regenerated", "description": "Notification message for when a password has been regenerated" }, - "saveLoginToBitwarden": { - "message": "Save login to Bitwarden?", + "saveToBitwarden": { + "message": "Save to Bitwarden", "description": "Confirmation message for saving a login to Bitwarden" }, "spaceCharacterDescriptor": { @@ -5169,6 +5201,12 @@ "changeAtRiskPassword": { "message": "Change at-risk password" }, + "settingsVaultOptions": { + "message": "Vault options" + }, + "emptyVaultDescription": { + "message": "The vault protects more than just your passwords. Store secure logins, IDs, cards and notes securely here." + }, "introCarouselLabel": { "message": "Welcome to Bitwarden" }, @@ -5195,5 +5233,50 @@ }, "secureDevicesBody": { "message": "Save unlimited passwords across unlimited devices with Bitwarden mobile, browser, and desktop apps." + }, + "emptyVaultNudgeTitle": { + "message": "Import existing passwords" + }, + "emptyVaultNudgeBody": { + "message": "Use the importer to quickly transfer logins to Bitwarden without manually adding them." + }, + "emptyVaultNudgeButton": { + "message": "Import now" + }, + "hasItemsVaultNudgeTitle": { + "message": "Welcome to your vault!" + }, + "hasItemsVaultNudgeBody": { + "message": "Autofill items for the current page\nFavorite items for easy access\nSearch your vault for something else" + }, + "newLoginNudgeTitle": { + "message": "Save time with autofill" + }, + "newLoginNudgeBody": { + "message": "Include a Website so this login appears as an autofill suggestion." + }, + "newCardNudgeTitle": { + "message": "Seamless online checkout" + }, + "newCardNudgeBody": { + "message": "With cards, easily autofill payment forms securely and accurately." + }, + "newIdentityNudgeTitle": { + "message": "Simplify creating accounts" + }, + "newIdentityNudgeBody": { + "message": "With identities, quickly autofill long registration or contact forms." + }, + "newNoteNudgeTitle": { + "message": "Keep your sensitive data safe" + }, + "newNoteNudgeBody": { + "message": "With notes, securely store sensitive data like banking or insurance details." + }, + "newSshNudgeTitle": { + "message": "Developer-friendly SSH access" + }, + "newSshNudgeBody": { + "message": "Store your keys and connect with the SSH agent for fast, encrypted authentication." } } diff --git a/apps/browser/src/_locales/bg/messages.json b/apps/browser/src/_locales/bg/messages.json index 457b0693a17..2bc42c0e6c9 100644 --- a/apps/browser/src/_locales/bg/messages.json +++ b/apps/browser/src/_locales/bg/messages.json @@ -2,6 +2,9 @@ "appName": { "message": "Битуорден (Bitwarden)" }, + "appLogoLabel": { + "message": "Лого на Битуорден" + }, "extName": { "message": "Bitwarden — управител на пароли", "description": "Extension name, MUST be less than 40 characters (Safari restriction)" @@ -883,6 +886,9 @@ "followTheStepsBelowToFinishLoggingIn": { "message": "Следвайте стъпките по-долу, за да завършите вписването." }, + "followTheStepsBelowToFinishLoggingInWithSecurityKey": { + "message": "Следвайте стъпките по-долу, за да завършите вписването си чрез устройството за удостоверяване." + }, "restartRegistration": { "message": "Рестартиране на регистрацията" }, @@ -1056,23 +1062,49 @@ "notificationAddSave": { "message": "Да, нека се запише сега" }, - "loginSaveSuccessDetails": { - "message": "Запазено в Битуорден: $USERNAME$.", + "notificationViewAria": { + "message": "Преглед на $ITEMNAME$, отваря се в нов прозорец", "placeholders": { - "username": { + "itemName": { "content": "$1" } }, - "description": "Shown to user after login is saved." + "description": "Aria label for the view button in notification bar confirmation message" }, - "loginUpdatedSuccessDetails": { - "message": "Обновено в Битуорден: $USERNAME$.", + "notificationEditTooltip": { + "message": "Редактиране преди запазване", + "description": "Tooltip and Aria label for edit button on cipher item" + }, + "newNotification": { + "message": "Ново известие" + }, + "labelWithNotification": { + "message": "$LABEL$: Ново известие", + "description": "Label for the notification with a new login suggestion.", "placeholders": { - "username": { + "label": { + "content": "$1", + "example": "Login" + } + } + }, + "loginSaveConfirmation": { + "message": "Запазено в Битуорден: $ITEMNAME$.", + "placeholders": { + "itemName": { "content": "$1" } }, - "description": "Shown to user after login is updated." + "description": "Shown to user after item is saved." + }, + "loginUpdatedConfirmation": { + "message": "Обновено в Битуорден: $ITEMNAME$.", + "placeholders": { + "itemName": { + "content": "$1" + } + }, + "description": "Shown to user after item is updated." }, "saveAsNewLoginAction": { "message": "Запазване като нов елемент за вписване", @@ -1082,12 +1114,12 @@ "message": "Обновяване на данните за вписване", "description": "Button text for updating an existing login entry." }, - "saveLoginPrompt": { - "message": "Запазване на данните за вписване?", + "saveLogin": { + "message": "Запазване на данните за вписване", "description": "Prompt asking the user if they want to save their login details." }, - "updateLoginPrompt": { - "message": "Да се обновят ли текущите данни за вписване?", + "updateLogin": { + "message": "Обновяване на текущите данни за вписване", "description": "Prompt asking the user if they want to update an existing login entry." }, "loginSaveSuccess": { @@ -4928,8 +4960,8 @@ "message": "Паролата е прегенерирана", "description": "Notification message for when a password has been regenerated" }, - "saveLoginToBitwarden": { - "message": "Запазване на данните за вписване в Битуорден?", + "saveToBitwarden": { + "message": "Запазване в Битуорден", "description": "Confirmation message for saving a login to Bitwarden" }, "spaceCharacterDescriptor": { @@ -5169,6 +5201,12 @@ "changeAtRiskPassword": { "message": "Промяна на парола в риск" }, + "settingsVaultOptions": { + "message": "Настройки на трезора" + }, + "emptyVaultDescription": { + "message": "Трезорът може да пази не само паролите Ви. Съхранявайте защитени данни за вход, идентификационни данни, карти и бележки." + }, "introCarouselLabel": { "message": "Добре дошли в Битуорден" }, @@ -5195,5 +5233,50 @@ }, "secureDevicesBody": { "message": "Съхранявайте неограничен брой пароли на множество устройства – с приложенията на Битуорден за мобилни телефони, браузър и компютър." + }, + "emptyVaultNudgeTitle": { + "message": "Внасяне на съществуващи пароли" + }, + "emptyVaultNudgeBody": { + "message": "Използвайте функцията за внасяне, за да прехвърлите лесно данните си за вписване в Битуорден, без да ги добавяте ръчно." + }, + "emptyVaultNudgeButton": { + "message": "Внасяне сега" + }, + "hasItemsVaultNudgeTitle": { + "message": "Добре дошли в трезора си!" + }, + "hasItemsVaultNudgeBody": { + "message": "Елементи за авт. попълване в текущата страница\nЛюбими елементи за лесен достъп\nПотърсете в трезора си за нещо друго" + }, + "newLoginNudgeTitle": { + "message": "Save time with autofill" + }, + "newLoginNudgeBody": { + "message": "Include a Website so this login appears as an autofill suggestion." + }, + "newCardNudgeTitle": { + "message": "Seamless online checkout" + }, + "newCardNudgeBody": { + "message": "With cards, easily autofill payment forms securely and accurately." + }, + "newIdentityNudgeTitle": { + "message": "Simplify creating accounts" + }, + "newIdentityNudgeBody": { + "message": "With identities, quickly autofill long registration or contact forms." + }, + "newNoteNudgeTitle": { + "message": "Keep your sensitive data safe" + }, + "newNoteNudgeBody": { + "message": "With notes, securely store sensitive data like banking or insurance details." + }, + "newSshNudgeTitle": { + "message": "Developer-friendly SSH access" + }, + "newSshNudgeBody": { + "message": "Store your keys and connect with the SSH agent for fast, encrypted authentication." } } diff --git a/apps/browser/src/_locales/bn/messages.json b/apps/browser/src/_locales/bn/messages.json index 275bb18e029..c14d5308a6b 100644 --- a/apps/browser/src/_locales/bn/messages.json +++ b/apps/browser/src/_locales/bn/messages.json @@ -2,6 +2,9 @@ "appName": { "message": "Bitwarden" }, + "appLogoLabel": { + "message": "Bitwarden logo" + }, "extName": { "message": "Bitwarden Password Manager", "description": "Extension name, MUST be less than 40 characters (Safari restriction)" @@ -883,6 +886,9 @@ "followTheStepsBelowToFinishLoggingIn": { "message": "Follow the steps below to finish logging in." }, + "followTheStepsBelowToFinishLoggingInWithSecurityKey": { + "message": "Follow the steps below to finish logging in with your security key." + }, "restartRegistration": { "message": "Restart registration" }, @@ -1056,23 +1062,49 @@ "notificationAddSave": { "message": "হ্যাঁ, এখনই সংরক্ষণ করুন" }, - "loginSaveSuccessDetails": { - "message": "$USERNAME$ saved to Bitwarden.", + "notificationViewAria": { + "message": "View $ITEMNAME$, opens in new window", "placeholders": { - "username": { + "itemName": { "content": "$1" } }, - "description": "Shown to user after login is saved." + "description": "Aria label for the view button in notification bar confirmation message" }, - "loginUpdatedSuccessDetails": { - "message": "$USERNAME$ updated in Bitwarden.", + "notificationEditTooltip": { + "message": "Edit before saving", + "description": "Tooltip and Aria label for edit button on cipher item" + }, + "newNotification": { + "message": "New notification" + }, + "labelWithNotification": { + "message": "$LABEL$: New notification", + "description": "Label for the notification with a new login suggestion.", "placeholders": { - "username": { + "label": { + "content": "$1", + "example": "Login" + } + } + }, + "loginSaveConfirmation": { + "message": "$ITEMNAME$ saved to Bitwarden.", + "placeholders": { + "itemName": { "content": "$1" } }, - "description": "Shown to user after login is updated." + "description": "Shown to user after item is saved." + }, + "loginUpdatedConfirmation": { + "message": "$ITEMNAME$ updated in Bitwarden.", + "placeholders": { + "itemName": { + "content": "$1" + } + }, + "description": "Shown to user after item is updated." }, "saveAsNewLoginAction": { "message": "Save as new login", @@ -1082,12 +1114,12 @@ "message": "Update login", "description": "Button text for updating an existing login entry." }, - "saveLoginPrompt": { - "message": "Save login?", + "saveLogin": { + "message": "Save login", "description": "Prompt asking the user if they want to save their login details." }, - "updateLoginPrompt": { - "message": "Update existing login?", + "updateLogin": { + "message": "Update existing login", "description": "Prompt asking the user if they want to update an existing login entry." }, "loginSaveSuccess": { @@ -4928,8 +4960,8 @@ "message": "Password regenerated", "description": "Notification message for when a password has been regenerated" }, - "saveLoginToBitwarden": { - "message": "Save login to Bitwarden?", + "saveToBitwarden": { + "message": "Save to Bitwarden", "description": "Confirmation message for saving a login to Bitwarden" }, "spaceCharacterDescriptor": { @@ -5169,6 +5201,12 @@ "changeAtRiskPassword": { "message": "Change at-risk password" }, + "settingsVaultOptions": { + "message": "Vault options" + }, + "emptyVaultDescription": { + "message": "The vault protects more than just your passwords. Store secure logins, IDs, cards and notes securely here." + }, "introCarouselLabel": { "message": "Welcome to Bitwarden" }, @@ -5195,5 +5233,50 @@ }, "secureDevicesBody": { "message": "Save unlimited passwords across unlimited devices with Bitwarden mobile, browser, and desktop apps." + }, + "emptyVaultNudgeTitle": { + "message": "Import existing passwords" + }, + "emptyVaultNudgeBody": { + "message": "Use the importer to quickly transfer logins to Bitwarden without manually adding them." + }, + "emptyVaultNudgeButton": { + "message": "Import now" + }, + "hasItemsVaultNudgeTitle": { + "message": "Welcome to your vault!" + }, + "hasItemsVaultNudgeBody": { + "message": "Autofill items for the current page\nFavorite items for easy access\nSearch your vault for something else" + }, + "newLoginNudgeTitle": { + "message": "Save time with autofill" + }, + "newLoginNudgeBody": { + "message": "Include a Website so this login appears as an autofill suggestion." + }, + "newCardNudgeTitle": { + "message": "Seamless online checkout" + }, + "newCardNudgeBody": { + "message": "With cards, easily autofill payment forms securely and accurately." + }, + "newIdentityNudgeTitle": { + "message": "Simplify creating accounts" + }, + "newIdentityNudgeBody": { + "message": "With identities, quickly autofill long registration or contact forms." + }, + "newNoteNudgeTitle": { + "message": "Keep your sensitive data safe" + }, + "newNoteNudgeBody": { + "message": "With notes, securely store sensitive data like banking or insurance details." + }, + "newSshNudgeTitle": { + "message": "Developer-friendly SSH access" + }, + "newSshNudgeBody": { + "message": "Store your keys and connect with the SSH agent for fast, encrypted authentication." } } diff --git a/apps/browser/src/_locales/bs/messages.json b/apps/browser/src/_locales/bs/messages.json index 687610c777e..eaa9ba863e0 100644 --- a/apps/browser/src/_locales/bs/messages.json +++ b/apps/browser/src/_locales/bs/messages.json @@ -2,6 +2,9 @@ "appName": { "message": "Bitwarden" }, + "appLogoLabel": { + "message": "Bitwarden logo" + }, "extName": { "message": "Bitwarden Password Manager", "description": "Extension name, MUST be less than 40 characters (Safari restriction)" @@ -883,6 +886,9 @@ "followTheStepsBelowToFinishLoggingIn": { "message": "Follow the steps below to finish logging in." }, + "followTheStepsBelowToFinishLoggingInWithSecurityKey": { + "message": "Follow the steps below to finish logging in with your security key." + }, "restartRegistration": { "message": "Restart registration" }, @@ -1056,23 +1062,49 @@ "notificationAddSave": { "message": "Save" }, - "loginSaveSuccessDetails": { - "message": "$USERNAME$ saved to Bitwarden.", + "notificationViewAria": { + "message": "View $ITEMNAME$, opens in new window", "placeholders": { - "username": { + "itemName": { "content": "$1" } }, - "description": "Shown to user after login is saved." + "description": "Aria label for the view button in notification bar confirmation message" }, - "loginUpdatedSuccessDetails": { - "message": "$USERNAME$ updated in Bitwarden.", + "notificationEditTooltip": { + "message": "Edit before saving", + "description": "Tooltip and Aria label for edit button on cipher item" + }, + "newNotification": { + "message": "New notification" + }, + "labelWithNotification": { + "message": "$LABEL$: New notification", + "description": "Label for the notification with a new login suggestion.", "placeholders": { - "username": { + "label": { + "content": "$1", + "example": "Login" + } + } + }, + "loginSaveConfirmation": { + "message": "$ITEMNAME$ saved to Bitwarden.", + "placeholders": { + "itemName": { "content": "$1" } }, - "description": "Shown to user after login is updated." + "description": "Shown to user after item is saved." + }, + "loginUpdatedConfirmation": { + "message": "$ITEMNAME$ updated in Bitwarden.", + "placeholders": { + "itemName": { + "content": "$1" + } + }, + "description": "Shown to user after item is updated." }, "saveAsNewLoginAction": { "message": "Save as new login", @@ -1082,12 +1114,12 @@ "message": "Update login", "description": "Button text for updating an existing login entry." }, - "saveLoginPrompt": { - "message": "Save login?", + "saveLogin": { + "message": "Save login", "description": "Prompt asking the user if they want to save their login details." }, - "updateLoginPrompt": { - "message": "Update existing login?", + "updateLogin": { + "message": "Update existing login", "description": "Prompt asking the user if they want to update an existing login entry." }, "loginSaveSuccess": { @@ -4928,8 +4960,8 @@ "message": "Password regenerated", "description": "Notification message for when a password has been regenerated" }, - "saveLoginToBitwarden": { - "message": "Save login to Bitwarden?", + "saveToBitwarden": { + "message": "Save to Bitwarden", "description": "Confirmation message for saving a login to Bitwarden" }, "spaceCharacterDescriptor": { @@ -5169,6 +5201,12 @@ "changeAtRiskPassword": { "message": "Change at-risk password" }, + "settingsVaultOptions": { + "message": "Vault options" + }, + "emptyVaultDescription": { + "message": "The vault protects more than just your passwords. Store secure logins, IDs, cards and notes securely here." + }, "introCarouselLabel": { "message": "Welcome to Bitwarden" }, @@ -5195,5 +5233,50 @@ }, "secureDevicesBody": { "message": "Save unlimited passwords across unlimited devices with Bitwarden mobile, browser, and desktop apps." + }, + "emptyVaultNudgeTitle": { + "message": "Import existing passwords" + }, + "emptyVaultNudgeBody": { + "message": "Use the importer to quickly transfer logins to Bitwarden without manually adding them." + }, + "emptyVaultNudgeButton": { + "message": "Import now" + }, + "hasItemsVaultNudgeTitle": { + "message": "Welcome to your vault!" + }, + "hasItemsVaultNudgeBody": { + "message": "Autofill items for the current page\nFavorite items for easy access\nSearch your vault for something else" + }, + "newLoginNudgeTitle": { + "message": "Save time with autofill" + }, + "newLoginNudgeBody": { + "message": "Include a Website so this login appears as an autofill suggestion." + }, + "newCardNudgeTitle": { + "message": "Seamless online checkout" + }, + "newCardNudgeBody": { + "message": "With cards, easily autofill payment forms securely and accurately." + }, + "newIdentityNudgeTitle": { + "message": "Simplify creating accounts" + }, + "newIdentityNudgeBody": { + "message": "With identities, quickly autofill long registration or contact forms." + }, + "newNoteNudgeTitle": { + "message": "Keep your sensitive data safe" + }, + "newNoteNudgeBody": { + "message": "With notes, securely store sensitive data like banking or insurance details." + }, + "newSshNudgeTitle": { + "message": "Developer-friendly SSH access" + }, + "newSshNudgeBody": { + "message": "Store your keys and connect with the SSH agent for fast, encrypted authentication." } } diff --git a/apps/browser/src/_locales/ca/messages.json b/apps/browser/src/_locales/ca/messages.json index d9a24c5e473..bbf6ccb9c41 100644 --- a/apps/browser/src/_locales/ca/messages.json +++ b/apps/browser/src/_locales/ca/messages.json @@ -2,6 +2,9 @@ "appName": { "message": "Bitwarden" }, + "appLogoLabel": { + "message": "Bitwarden logo" + }, "extName": { "message": "Bitwarden - Gestor de contrasenyes", "description": "Extension name, MUST be less than 40 characters (Safari restriction)" @@ -883,6 +886,9 @@ "followTheStepsBelowToFinishLoggingIn": { "message": "Follow the steps below to finish logging in." }, + "followTheStepsBelowToFinishLoggingInWithSecurityKey": { + "message": "Follow the steps below to finish logging in with your security key." + }, "restartRegistration": { "message": "Reinicia el registre" }, @@ -1056,23 +1062,49 @@ "notificationAddSave": { "message": "Guarda" }, - "loginSaveSuccessDetails": { - "message": "$USERNAME$ saved to Bitwarden.", + "notificationViewAria": { + "message": "View $ITEMNAME$, opens in new window", "placeholders": { - "username": { + "itemName": { "content": "$1" } }, - "description": "Shown to user after login is saved." + "description": "Aria label for the view button in notification bar confirmation message" }, - "loginUpdatedSuccessDetails": { - "message": "$USERNAME$ updated in Bitwarden.", + "notificationEditTooltip": { + "message": "Edit before saving", + "description": "Tooltip and Aria label for edit button on cipher item" + }, + "newNotification": { + "message": "New notification" + }, + "labelWithNotification": { + "message": "$LABEL$: New notification", + "description": "Label for the notification with a new login suggestion.", "placeholders": { - "username": { + "label": { + "content": "$1", + "example": "Login" + } + } + }, + "loginSaveConfirmation": { + "message": "$ITEMNAME$ saved to Bitwarden.", + "placeholders": { + "itemName": { "content": "$1" } }, - "description": "Shown to user after login is updated." + "description": "Shown to user after item is saved." + }, + "loginUpdatedConfirmation": { + "message": "$ITEMNAME$ updated in Bitwarden.", + "placeholders": { + "itemName": { + "content": "$1" + } + }, + "description": "Shown to user after item is updated." }, "saveAsNewLoginAction": { "message": "Save as new login", @@ -1082,12 +1114,12 @@ "message": "Update login", "description": "Button text for updating an existing login entry." }, - "saveLoginPrompt": { - "message": "Save login?", + "saveLogin": { + "message": "Save login", "description": "Prompt asking the user if they want to save their login details." }, - "updateLoginPrompt": { - "message": "Update existing login?", + "updateLogin": { + "message": "Update existing login", "description": "Prompt asking the user if they want to update an existing login entry." }, "loginSaveSuccess": { @@ -4928,8 +4960,8 @@ "message": "Contrasenya regenerada", "description": "Notification message for when a password has been regenerated" }, - "saveLoginToBitwarden": { - "message": "Save login to Bitwarden?", + "saveToBitwarden": { + "message": "Save to Bitwarden", "description": "Confirmation message for saving a login to Bitwarden" }, "spaceCharacterDescriptor": { @@ -5169,6 +5201,12 @@ "changeAtRiskPassword": { "message": "Change at-risk password" }, + "settingsVaultOptions": { + "message": "Vault options" + }, + "emptyVaultDescription": { + "message": "The vault protects more than just your passwords. Store secure logins, IDs, cards and notes securely here." + }, "introCarouselLabel": { "message": "Welcome to Bitwarden" }, @@ -5195,5 +5233,50 @@ }, "secureDevicesBody": { "message": "Save unlimited passwords across unlimited devices with Bitwarden mobile, browser, and desktop apps." + }, + "emptyVaultNudgeTitle": { + "message": "Import existing passwords" + }, + "emptyVaultNudgeBody": { + "message": "Use the importer to quickly transfer logins to Bitwarden without manually adding them." + }, + "emptyVaultNudgeButton": { + "message": "Import now" + }, + "hasItemsVaultNudgeTitle": { + "message": "Welcome to your vault!" + }, + "hasItemsVaultNudgeBody": { + "message": "Autofill items for the current page\nFavorite items for easy access\nSearch your vault for something else" + }, + "newLoginNudgeTitle": { + "message": "Save time with autofill" + }, + "newLoginNudgeBody": { + "message": "Include a Website so this login appears as an autofill suggestion." + }, + "newCardNudgeTitle": { + "message": "Seamless online checkout" + }, + "newCardNudgeBody": { + "message": "With cards, easily autofill payment forms securely and accurately." + }, + "newIdentityNudgeTitle": { + "message": "Simplify creating accounts" + }, + "newIdentityNudgeBody": { + "message": "With identities, quickly autofill long registration or contact forms." + }, + "newNoteNudgeTitle": { + "message": "Keep your sensitive data safe" + }, + "newNoteNudgeBody": { + "message": "With notes, securely store sensitive data like banking or insurance details." + }, + "newSshNudgeTitle": { + "message": "Developer-friendly SSH access" + }, + "newSshNudgeBody": { + "message": "Store your keys and connect with the SSH agent for fast, encrypted authentication." } } diff --git a/apps/browser/src/_locales/cs/messages.json b/apps/browser/src/_locales/cs/messages.json index 2ed34c8c71a..dfbc0acea76 100644 --- a/apps/browser/src/_locales/cs/messages.json +++ b/apps/browser/src/_locales/cs/messages.json @@ -2,6 +2,9 @@ "appName": { "message": "Bitwarden" }, + "appLogoLabel": { + "message": "Logo Bitwarden" + }, "extName": { "message": "Bitwarden - Správce hesel", "description": "Extension name, MUST be less than 40 characters (Safari restriction)" @@ -883,6 +886,9 @@ "followTheStepsBelowToFinishLoggingIn": { "message": "Postupujte podle kroků níže pro dokončení přihlášení." }, + "followTheStepsBelowToFinishLoggingInWithSecurityKey": { + "message": "Postupujte podle následujících kroků pro dokončení přihlášení Vaším bezpečnostním klíčem." + }, "restartRegistration": { "message": "Restartovat registraci" }, @@ -1056,23 +1062,49 @@ "notificationAddSave": { "message": "Uložit" }, - "loginSaveSuccessDetails": { - "message": "$USERNAME$ uloženo do Bitwardenu.", + "notificationViewAria": { + "message": "Zobrazit $ITEMNAME$, otevře se v novém okně", "placeholders": { - "username": { + "itemName": { "content": "$1" } }, - "description": "Shown to user after login is saved." + "description": "Aria label for the view button in notification bar confirmation message" }, - "loginUpdatedSuccessDetails": { - "message": "$USERNAME$ aktualizováno v Bitwardenu.", + "notificationEditTooltip": { + "message": "Upravit před uložením", + "description": "Tooltip and Aria label for edit button on cipher item" + }, + "newNotification": { + "message": "Nové oznámení" + }, + "labelWithNotification": { + "message": "$LABEL$: Nové oznámení", + "description": "Label for the notification with a new login suggestion.", "placeholders": { - "username": { + "label": { + "content": "$1", + "example": "Login" + } + } + }, + "loginSaveConfirmation": { + "message": "$ITEMNAME$ - uloženo do Bitwardenu.", + "placeholders": { + "itemName": { "content": "$1" } }, - "description": "Shown to user after login is updated." + "description": "Shown to user after item is saved." + }, + "loginUpdatedConfirmation": { + "message": "$ITEMNAME$ - aktualizováno v Bitwardenu.", + "placeholders": { + "itemName": { + "content": "$1" + } + }, + "description": "Shown to user after item is updated." }, "saveAsNewLoginAction": { "message": "Uložit jako nové přihlašovací údaje", @@ -1082,12 +1114,12 @@ "message": "Aktualizovat přihlašovací údaje", "description": "Button text for updating an existing login entry." }, - "saveLoginPrompt": { - "message": "Uložit přihlašovací údaje?", + "saveLogin": { + "message": "Uložit přihlašovací údaje", "description": "Prompt asking the user if they want to save their login details." }, - "updateLoginPrompt": { - "message": "Aktualizovat existující přihlašovací údaje?", + "updateLogin": { + "message": "Aktualizovat existující přihlašovací údaje", "description": "Prompt asking the user if they want to update an existing login entry." }, "loginSaveSuccess": { @@ -4928,8 +4960,8 @@ "message": "Heslo bylo znovu vygenerováno", "description": "Notification message for when a password has been regenerated" }, - "saveLoginToBitwarden": { - "message": "Uložit přihlášení do Bitwardenu?", + "saveToBitwarden": { + "message": "Uložit do Bitwardenu", "description": "Confirmation message for saving a login to Bitwarden" }, "spaceCharacterDescriptor": { @@ -5169,6 +5201,12 @@ "changeAtRiskPassword": { "message": "Změnit ohrožené heslo" }, + "settingsVaultOptions": { + "message": "Volby trezoru" + }, + "emptyVaultDescription": { + "message": "Trezor chrání více než jen Vaše hesla. Bezpečně zde uložte zabezpečená přihlášení, ID, karty a poznámky." + }, "introCarouselLabel": { "message": "Vítejte v Bitwardenu" }, @@ -5195,5 +5233,50 @@ }, "secureDevicesBody": { "message": "Uložte neomezená hesla na neomezených zařízeních s Bitwardenem na mobilu, prohlížeči a desktopové aplikaci." + }, + "emptyVaultNudgeTitle": { + "message": "Importovat existující hesla" + }, + "emptyVaultNudgeBody": { + "message": "Pomocí importu rychle přenesete přihlašovací údaje do Bitwardenu a to bez jejich ručního přidání." + }, + "emptyVaultNudgeButton": { + "message": "Importovat nyní" + }, + "hasItemsVaultNudgeTitle": { + "message": "Vítejte ve Vašem trezoru!" + }, + "hasItemsVaultNudgeBody": { + "message": "Položky automatického vyplňování pro aktuální stránku\nOblíbené položky pro snadný přístup\nNajděte v trezoru něco jiného" + }, + "newLoginNudgeTitle": { + "message": "Ušetřete čas s automatickým vyplňováním" + }, + "newLoginNudgeBody": { + "message": "Zahrne webovou stránku, takže se toto přihlášení objeví jako návrh automatického vyplňování." + }, + "newCardNudgeTitle": { + "message": "Bezproblémová online pokladna" + }, + "newCardNudgeBody": { + "message": "Karty - snadné, bezpečné a přesné vyplňování platebních formulářů." + }, + "newIdentityNudgeTitle": { + "message": "Jednodušší vytváření účtů" + }, + "newIdentityNudgeBody": { + "message": "Identity - rychlé automatické vyplňování dlouhých registračních nebo kontaktních formulářů." + }, + "newNoteNudgeTitle": { + "message": "Udržujte svá citlivá data v bezpečí" + }, + "newNoteNudgeBody": { + "message": "Poznámky - bezpečné ukládání citlivých údajů, jako jsou bankovní nebo pojišťovací údaje." + }, + "newSshNudgeTitle": { + "message": "Přístup SSH pro vývojáře" + }, + "newSshNudgeBody": { + "message": "Uložte své klíče a připojte se k SSH agentovi pro rychlé a šifrované ověření." } } diff --git a/apps/browser/src/_locales/cy/messages.json b/apps/browser/src/_locales/cy/messages.json index eb07358540d..5fa135a929d 100644 --- a/apps/browser/src/_locales/cy/messages.json +++ b/apps/browser/src/_locales/cy/messages.json @@ -2,6 +2,9 @@ "appName": { "message": "Bitwarden" }, + "appLogoLabel": { + "message": "Bitwarden logo" + }, "extName": { "message": "Rheolydd cyfrineiriau Bitwarden", "description": "Extension name, MUST be less than 40 characters (Safari restriction)" @@ -883,6 +886,9 @@ "followTheStepsBelowToFinishLoggingIn": { "message": "Follow the steps below to finish logging in." }, + "followTheStepsBelowToFinishLoggingInWithSecurityKey": { + "message": "Follow the steps below to finish logging in with your security key." + }, "restartRegistration": { "message": "Restart registration" }, @@ -1056,23 +1062,49 @@ "notificationAddSave": { "message": "Cadw" }, - "loginSaveSuccessDetails": { - "message": "$USERNAME$ saved to Bitwarden.", + "notificationViewAria": { + "message": "View $ITEMNAME$, opens in new window", "placeholders": { - "username": { + "itemName": { "content": "$1" } }, - "description": "Shown to user after login is saved." + "description": "Aria label for the view button in notification bar confirmation message" }, - "loginUpdatedSuccessDetails": { - "message": "$USERNAME$ updated in Bitwarden.", + "notificationEditTooltip": { + "message": "Edit before saving", + "description": "Tooltip and Aria label for edit button on cipher item" + }, + "newNotification": { + "message": "New notification" + }, + "labelWithNotification": { + "message": "$LABEL$: New notification", + "description": "Label for the notification with a new login suggestion.", "placeholders": { - "username": { + "label": { + "content": "$1", + "example": "Login" + } + } + }, + "loginSaveConfirmation": { + "message": "$ITEMNAME$ saved to Bitwarden.", + "placeholders": { + "itemName": { "content": "$1" } }, - "description": "Shown to user after login is updated." + "description": "Shown to user after item is saved." + }, + "loginUpdatedConfirmation": { + "message": "$ITEMNAME$ updated in Bitwarden.", + "placeholders": { + "itemName": { + "content": "$1" + } + }, + "description": "Shown to user after item is updated." }, "saveAsNewLoginAction": { "message": "Cadw fel manylion mewngofnodi newydd", @@ -1082,12 +1114,12 @@ "message": "Update login", "description": "Button text for updating an existing login entry." }, - "saveLoginPrompt": { - "message": "Save login?", + "saveLogin": { + "message": "Save login", "description": "Prompt asking the user if they want to save their login details." }, - "updateLoginPrompt": { - "message": "Update existing login?", + "updateLogin": { + "message": "Update existing login", "description": "Prompt asking the user if they want to update an existing login entry." }, "loginSaveSuccess": { @@ -4928,8 +4960,8 @@ "message": "Password regenerated", "description": "Notification message for when a password has been regenerated" }, - "saveLoginToBitwarden": { - "message": "Save login to Bitwarden?", + "saveToBitwarden": { + "message": "Save to Bitwarden", "description": "Confirmation message for saving a login to Bitwarden" }, "spaceCharacterDescriptor": { @@ -5169,6 +5201,12 @@ "changeAtRiskPassword": { "message": "Change at-risk password" }, + "settingsVaultOptions": { + "message": "Vault options" + }, + "emptyVaultDescription": { + "message": "The vault protects more than just your passwords. Store secure logins, IDs, cards and notes securely here." + }, "introCarouselLabel": { "message": "Welcome to Bitwarden" }, @@ -5195,5 +5233,50 @@ }, "secureDevicesBody": { "message": "Save unlimited passwords across unlimited devices with Bitwarden mobile, browser, and desktop apps." + }, + "emptyVaultNudgeTitle": { + "message": "Import existing passwords" + }, + "emptyVaultNudgeBody": { + "message": "Use the importer to quickly transfer logins to Bitwarden without manually adding them." + }, + "emptyVaultNudgeButton": { + "message": "Import now" + }, + "hasItemsVaultNudgeTitle": { + "message": "Welcome to your vault!" + }, + "hasItemsVaultNudgeBody": { + "message": "Autofill items for the current page\nFavorite items for easy access\nSearch your vault for something else" + }, + "newLoginNudgeTitle": { + "message": "Save time with autofill" + }, + "newLoginNudgeBody": { + "message": "Include a Website so this login appears as an autofill suggestion." + }, + "newCardNudgeTitle": { + "message": "Seamless online checkout" + }, + "newCardNudgeBody": { + "message": "With cards, easily autofill payment forms securely and accurately." + }, + "newIdentityNudgeTitle": { + "message": "Simplify creating accounts" + }, + "newIdentityNudgeBody": { + "message": "With identities, quickly autofill long registration or contact forms." + }, + "newNoteNudgeTitle": { + "message": "Keep your sensitive data safe" + }, + "newNoteNudgeBody": { + "message": "With notes, securely store sensitive data like banking or insurance details." + }, + "newSshNudgeTitle": { + "message": "Developer-friendly SSH access" + }, + "newSshNudgeBody": { + "message": "Store your keys and connect with the SSH agent for fast, encrypted authentication." } } diff --git a/apps/browser/src/_locales/da/messages.json b/apps/browser/src/_locales/da/messages.json index 914c74411e4..f1c55f3557d 100644 --- a/apps/browser/src/_locales/da/messages.json +++ b/apps/browser/src/_locales/da/messages.json @@ -2,6 +2,9 @@ "appName": { "message": "Bitwarden" }, + "appLogoLabel": { + "message": "Bitwarden logo" + }, "extName": { "message": "Bitwarden Adgangskodehåndtering", "description": "Extension name, MUST be less than 40 characters (Safari restriction)" @@ -883,6 +886,9 @@ "followTheStepsBelowToFinishLoggingIn": { "message": "Follow the steps below to finish logging in." }, + "followTheStepsBelowToFinishLoggingInWithSecurityKey": { + "message": "Follow the steps below to finish logging in with your security key." + }, "restartRegistration": { "message": "Genstart registrering" }, @@ -1056,23 +1062,49 @@ "notificationAddSave": { "message": "Gem" }, - "loginSaveSuccessDetails": { - "message": "$USERNAME$ saved to Bitwarden.", + "notificationViewAria": { + "message": "View $ITEMNAME$, opens in new window", "placeholders": { - "username": { + "itemName": { "content": "$1" } }, - "description": "Shown to user after login is saved." + "description": "Aria label for the view button in notification bar confirmation message" }, - "loginUpdatedSuccessDetails": { - "message": "$USERNAME$ updated in Bitwarden.", + "notificationEditTooltip": { + "message": "Edit before saving", + "description": "Tooltip and Aria label for edit button on cipher item" + }, + "newNotification": { + "message": "New notification" + }, + "labelWithNotification": { + "message": "$LABEL$: New notification", + "description": "Label for the notification with a new login suggestion.", "placeholders": { - "username": { + "label": { + "content": "$1", + "example": "Login" + } + } + }, + "loginSaveConfirmation": { + "message": "$ITEMNAME$ saved to Bitwarden.", + "placeholders": { + "itemName": { "content": "$1" } }, - "description": "Shown to user after login is updated." + "description": "Shown to user after item is saved." + }, + "loginUpdatedConfirmation": { + "message": "$ITEMNAME$ updated in Bitwarden.", + "placeholders": { + "itemName": { + "content": "$1" + } + }, + "description": "Shown to user after item is updated." }, "saveAsNewLoginAction": { "message": "Save as new login", @@ -1082,12 +1114,12 @@ "message": "Update login", "description": "Button text for updating an existing login entry." }, - "saveLoginPrompt": { - "message": "Save login?", + "saveLogin": { + "message": "Save login", "description": "Prompt asking the user if they want to save their login details." }, - "updateLoginPrompt": { - "message": "Update existing login?", + "updateLogin": { + "message": "Update existing login", "description": "Prompt asking the user if they want to update an existing login entry." }, "loginSaveSuccess": { @@ -4928,8 +4960,8 @@ "message": "Adgangskode genereret igen", "description": "Notification message for when a password has been regenerated" }, - "saveLoginToBitwarden": { - "message": "Gem login til Bitwarden?", + "saveToBitwarden": { + "message": "Save to Bitwarden", "description": "Confirmation message for saving a login to Bitwarden" }, "spaceCharacterDescriptor": { @@ -5169,6 +5201,12 @@ "changeAtRiskPassword": { "message": "Change at-risk password" }, + "settingsVaultOptions": { + "message": "Vault options" + }, + "emptyVaultDescription": { + "message": "The vault protects more than just your passwords. Store secure logins, IDs, cards and notes securely here." + }, "introCarouselLabel": { "message": "Welcome to Bitwarden" }, @@ -5195,5 +5233,50 @@ }, "secureDevicesBody": { "message": "Save unlimited passwords across unlimited devices with Bitwarden mobile, browser, and desktop apps." + }, + "emptyVaultNudgeTitle": { + "message": "Import existing passwords" + }, + "emptyVaultNudgeBody": { + "message": "Use the importer to quickly transfer logins to Bitwarden without manually adding them." + }, + "emptyVaultNudgeButton": { + "message": "Import now" + }, + "hasItemsVaultNudgeTitle": { + "message": "Welcome to your vault!" + }, + "hasItemsVaultNudgeBody": { + "message": "Autofill items for the current page\nFavorite items for easy access\nSearch your vault for something else" + }, + "newLoginNudgeTitle": { + "message": "Save time with autofill" + }, + "newLoginNudgeBody": { + "message": "Include a Website so this login appears as an autofill suggestion." + }, + "newCardNudgeTitle": { + "message": "Seamless online checkout" + }, + "newCardNudgeBody": { + "message": "With cards, easily autofill payment forms securely and accurately." + }, + "newIdentityNudgeTitle": { + "message": "Simplify creating accounts" + }, + "newIdentityNudgeBody": { + "message": "With identities, quickly autofill long registration or contact forms." + }, + "newNoteNudgeTitle": { + "message": "Keep your sensitive data safe" + }, + "newNoteNudgeBody": { + "message": "With notes, securely store sensitive data like banking or insurance details." + }, + "newSshNudgeTitle": { + "message": "Developer-friendly SSH access" + }, + "newSshNudgeBody": { + "message": "Store your keys and connect with the SSH agent for fast, encrypted authentication." } } diff --git a/apps/browser/src/_locales/de/messages.json b/apps/browser/src/_locales/de/messages.json index 0edf5edfa3d..251a0323c60 100644 --- a/apps/browser/src/_locales/de/messages.json +++ b/apps/browser/src/_locales/de/messages.json @@ -2,6 +2,9 @@ "appName": { "message": "Bitwarden" }, + "appLogoLabel": { + "message": "Bitwarden-Logo" + }, "extName": { "message": "Bitwarden Passwortmanager", "description": "Extension name, MUST be less than 40 characters (Safari restriction)" @@ -883,6 +886,9 @@ "followTheStepsBelowToFinishLoggingIn": { "message": "Folge den Schritten unten, um die Anmeldung abzuschließen." }, + "followTheStepsBelowToFinishLoggingInWithSecurityKey": { + "message": "Folge den Schritten unten, um die Anmeldung mit deinem Sicherheitsschlüssel abzuschließen." + }, "restartRegistration": { "message": "Registrierung neu starten" }, @@ -1056,23 +1062,49 @@ "notificationAddSave": { "message": "Ja, jetzt speichern" }, - "loginSaveSuccessDetails": { - "message": "$USERNAME$ in Bitwarden gespeichert.", + "notificationViewAria": { + "message": "$ITEMNAME$ anzeigen, öffnet sich in neuem Fenster", "placeholders": { - "username": { + "itemName": { "content": "$1" } }, - "description": "Shown to user after login is saved." + "description": "Aria label for the view button in notification bar confirmation message" }, - "loginUpdatedSuccessDetails": { - "message": "$USERNAME$ in Bitwarden aktualisiert.", + "notificationEditTooltip": { + "message": "Vor dem Speichern bearbeiten", + "description": "Tooltip and Aria label for edit button on cipher item" + }, + "newNotification": { + "message": "Neue Benachrichtigung" + }, + "labelWithNotification": { + "message": "$LABEL$: Neue Benachrichtigung", + "description": "Label for the notification with a new login suggestion.", "placeholders": { - "username": { + "label": { + "content": "$1", + "example": "Login" + } + } + }, + "loginSaveConfirmation": { + "message": "$ITEMNAME$ in Bitwarden gespeichert.", + "placeholders": { + "itemName": { "content": "$1" } }, - "description": "Shown to user after login is updated." + "description": "Shown to user after item is saved." + }, + "loginUpdatedConfirmation": { + "message": "$ITEMNAME$ in Bitwarden aktualisiert.", + "placeholders": { + "itemName": { + "content": "$1" + } + }, + "description": "Shown to user after item is updated." }, "saveAsNewLoginAction": { "message": "Als neue Zugangsdaten speichern", @@ -1082,12 +1114,12 @@ "message": "Zugangsdaten aktualisieren", "description": "Button text for updating an existing login entry." }, - "saveLoginPrompt": { - "message": "Zugangsdaten speichern?", + "saveLogin": { + "message": "Zugangsdaten speichern", "description": "Prompt asking the user if they want to save their login details." }, - "updateLoginPrompt": { - "message": "Bestehende Zugangsdaten aktualisieren?", + "updateLogin": { + "message": "Bestehende Zugangsdaten aktualisieren", "description": "Prompt asking the user if they want to update an existing login entry." }, "loginSaveSuccess": { @@ -1099,7 +1131,7 @@ "description": "Message displayed when login details are successfully updated." }, "loginUpdateTaskSuccess": { - "message": "Great job! You took the steps to make you and $ORGANIZATION$ more secure.", + "message": "Gut gemacht! Du hast die Schritte unternommen, um dich und $ORGANIZATION$ sicherer zu machen.", "placeholders": { "organization": { "content": "$1" @@ -1108,7 +1140,7 @@ "description": "Shown to user after login is updated." }, "loginUpdateTaskSuccessAdditional": { - "message": "Thank you for making $ORGANIZATION$ more secure. You have $TASK_COUNT$ more passwords to update.", + "message": "Vielen Dank, dass du $ORGANIZATION$ sicherer gemacht hast. Du hast $TASK_COUNT$ weitere Passwörter zum Aktualisieren.", "placeholders": { "organization": { "content": "$1" @@ -1120,7 +1152,7 @@ "description": "Shown to user after login is updated." }, "nextSecurityTaskAction": { - "message": "Change next password", + "message": "Nächstes Passwort ändern", "description": "Message prompting user to undertake completion of another security task." }, "saveFailure": { @@ -4928,8 +4960,8 @@ "message": "Passwort neu generiert", "description": "Notification message for when a password has been regenerated" }, - "saveLoginToBitwarden": { - "message": "Zugangsdaten in Bitwarden speichern?", + "saveToBitwarden": { + "message": "In Bitwarden speichern", "description": "Confirmation message for saving a login to Bitwarden" }, "spaceCharacterDescriptor": { @@ -5169,6 +5201,12 @@ "changeAtRiskPassword": { "message": "Gefährdetes Passwort ändern" }, + "settingsVaultOptions": { + "message": "Vault options" + }, + "emptyVaultDescription": { + "message": "The vault protects more than just your passwords. Store secure logins, IDs, cards and notes securely here." + }, "introCarouselLabel": { "message": "Willkommen bei Bitwarden" }, @@ -5195,5 +5233,50 @@ }, "secureDevicesBody": { "message": "Speicher eine unbegrenzte Anzahl von Passwörtern auf unbegrenzt vielen Geräten mit Bitwarden-Apps für Smartphones, Browser und Desktop." + }, + "emptyVaultNudgeTitle": { + "message": "Import existing passwords" + }, + "emptyVaultNudgeBody": { + "message": "Use the importer to quickly transfer logins to Bitwarden without manually adding them." + }, + "emptyVaultNudgeButton": { + "message": "Import now" + }, + "hasItemsVaultNudgeTitle": { + "message": "Welcome to your vault!" + }, + "hasItemsVaultNudgeBody": { + "message": "Autofill items for the current page\nFavorite items for easy access\nSearch your vault for something else" + }, + "newLoginNudgeTitle": { + "message": "Save time with autofill" + }, + "newLoginNudgeBody": { + "message": "Include a Website so this login appears as an autofill suggestion." + }, + "newCardNudgeTitle": { + "message": "Seamless online checkout" + }, + "newCardNudgeBody": { + "message": "With cards, easily autofill payment forms securely and accurately." + }, + "newIdentityNudgeTitle": { + "message": "Simplify creating accounts" + }, + "newIdentityNudgeBody": { + "message": "With identities, quickly autofill long registration or contact forms." + }, + "newNoteNudgeTitle": { + "message": "Keep your sensitive data safe" + }, + "newNoteNudgeBody": { + "message": "With notes, securely store sensitive data like banking or insurance details." + }, + "newSshNudgeTitle": { + "message": "Developer-friendly SSH access" + }, + "newSshNudgeBody": { + "message": "Store your keys and connect with the SSH agent for fast, encrypted authentication." } } diff --git a/apps/browser/src/_locales/el/messages.json b/apps/browser/src/_locales/el/messages.json index 1c15f70d40c..691f546b327 100644 --- a/apps/browser/src/_locales/el/messages.json +++ b/apps/browser/src/_locales/el/messages.json @@ -2,6 +2,9 @@ "appName": { "message": "Bitwarden" }, + "appLogoLabel": { + "message": "Bitwarden logo" + }, "extName": { "message": "Διαχειριστής Κωδικών Πρόσβασης Bitwarden", "description": "Extension name, MUST be less than 40 characters (Safari restriction)" @@ -883,6 +886,9 @@ "followTheStepsBelowToFinishLoggingIn": { "message": "Follow the steps below to finish logging in." }, + "followTheStepsBelowToFinishLoggingInWithSecurityKey": { + "message": "Follow the steps below to finish logging in with your security key." + }, "restartRegistration": { "message": "Επανεκκίνηση εγγραφής" }, @@ -1056,23 +1062,49 @@ "notificationAddSave": { "message": "Ναι, Αποθήκευση Τώρα" }, - "loginSaveSuccessDetails": { - "message": "$USERNAME$ saved to Bitwarden.", + "notificationViewAria": { + "message": "View $ITEMNAME$, opens in new window", "placeholders": { - "username": { + "itemName": { "content": "$1" } }, - "description": "Shown to user after login is saved." + "description": "Aria label for the view button in notification bar confirmation message" }, - "loginUpdatedSuccessDetails": { - "message": "$USERNAME$ updated in Bitwarden.", + "notificationEditTooltip": { + "message": "Edit before saving", + "description": "Tooltip and Aria label for edit button on cipher item" + }, + "newNotification": { + "message": "New notification" + }, + "labelWithNotification": { + "message": "$LABEL$: New notification", + "description": "Label for the notification with a new login suggestion.", "placeholders": { - "username": { + "label": { + "content": "$1", + "example": "Login" + } + } + }, + "loginSaveConfirmation": { + "message": "$ITEMNAME$ saved to Bitwarden.", + "placeholders": { + "itemName": { "content": "$1" } }, - "description": "Shown to user after login is updated." + "description": "Shown to user after item is saved." + }, + "loginUpdatedConfirmation": { + "message": "$ITEMNAME$ updated in Bitwarden.", + "placeholders": { + "itemName": { + "content": "$1" + } + }, + "description": "Shown to user after item is updated." }, "saveAsNewLoginAction": { "message": "Save as new login", @@ -1082,12 +1114,12 @@ "message": "Update login", "description": "Button text for updating an existing login entry." }, - "saveLoginPrompt": { - "message": "Save login?", + "saveLogin": { + "message": "Save login", "description": "Prompt asking the user if they want to save their login details." }, - "updateLoginPrompt": { - "message": "Update existing login?", + "updateLogin": { + "message": "Update existing login", "description": "Prompt asking the user if they want to update an existing login entry." }, "loginSaveSuccess": { @@ -4928,8 +4960,8 @@ "message": "Ο κωδικός επαναδημιουργήθηκε", "description": "Notification message for when a password has been regenerated" }, - "saveLoginToBitwarden": { - "message": "Αποθήκευση σύνδεσης στο Bitwarden;", + "saveToBitwarden": { + "message": "Save to Bitwarden", "description": "Confirmation message for saving a login to Bitwarden" }, "spaceCharacterDescriptor": { @@ -5169,6 +5201,12 @@ "changeAtRiskPassword": { "message": "Change at-risk password" }, + "settingsVaultOptions": { + "message": "Vault options" + }, + "emptyVaultDescription": { + "message": "The vault protects more than just your passwords. Store secure logins, IDs, cards and notes securely here." + }, "introCarouselLabel": { "message": "Welcome to Bitwarden" }, @@ -5195,5 +5233,50 @@ }, "secureDevicesBody": { "message": "Save unlimited passwords across unlimited devices with Bitwarden mobile, browser, and desktop apps." + }, + "emptyVaultNudgeTitle": { + "message": "Import existing passwords" + }, + "emptyVaultNudgeBody": { + "message": "Use the importer to quickly transfer logins to Bitwarden without manually adding them." + }, + "emptyVaultNudgeButton": { + "message": "Import now" + }, + "hasItemsVaultNudgeTitle": { + "message": "Welcome to your vault!" + }, + "hasItemsVaultNudgeBody": { + "message": "Autofill items for the current page\nFavorite items for easy access\nSearch your vault for something else" + }, + "newLoginNudgeTitle": { + "message": "Save time with autofill" + }, + "newLoginNudgeBody": { + "message": "Include a Website so this login appears as an autofill suggestion." + }, + "newCardNudgeTitle": { + "message": "Seamless online checkout" + }, + "newCardNudgeBody": { + "message": "With cards, easily autofill payment forms securely and accurately." + }, + "newIdentityNudgeTitle": { + "message": "Simplify creating accounts" + }, + "newIdentityNudgeBody": { + "message": "With identities, quickly autofill long registration or contact forms." + }, + "newNoteNudgeTitle": { + "message": "Keep your sensitive data safe" + }, + "newNoteNudgeBody": { + "message": "With notes, securely store sensitive data like banking or insurance details." + }, + "newSshNudgeTitle": { + "message": "Developer-friendly SSH access" + }, + "newSshNudgeBody": { + "message": "Store your keys and connect with the SSH agent for fast, encrypted authentication." } } diff --git a/apps/browser/src/_locales/en/messages.json b/apps/browser/src/_locales/en/messages.json index 164fb6a3361..81c603d44a8 100644 --- a/apps/browser/src/_locales/en/messages.json +++ b/apps/browser/src/_locales/en/messages.json @@ -192,7 +192,7 @@ "message": "Copy", "description": "Copy to clipboard" }, - "fill":{ + "fill": { "message": "Fill", "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." }, @@ -913,6 +913,9 @@ "followTheStepsBelowToFinishLoggingIn": { "message": "Follow the steps below to finish logging in." }, + "followTheStepsBelowToFinishLoggingInWithSecurityKey": { + "message": "Follow the steps below to finish logging in with your security key." + }, "restartRegistration": { "message": "Restart registration" }, @@ -1086,23 +1089,49 @@ "notificationAddSave": { "message": "Save" }, - "loginSaveSuccessDetails": { - "message": "$USERNAME$ saved to Bitwarden.", - "placeholders": { - "username": { - "content": "$1" - } - }, - "description": "Shown to user after login is saved." + "notificationViewAria": { + "message": "View $ITEMNAME$, opens in new window", + "placeholders": { + "itemName": { + "content": "$1" + } + }, + "description": "Aria label for the view button in notification bar confirmation message" }, - "loginUpdatedSuccessDetails": { - "message": "$USERNAME$ updated in Bitwarden.", - "placeholders": { - "username": { - "content": "$1" - } - }, - "description": "Shown to user after login is updated." + "notificationEditTooltip": { + "message": "Edit before saving", + "description": "Tooltip and Aria label for edit button on cipher item" + }, + "newNotification": { + "message": "New notification" + }, + "labelWithNotification": { + "message": "$LABEL$: New notification", + "description": "Label for the notification with a new login suggestion.", + "placeholders": { + "label": { + "content": "$1", + "example": "Login" + } + } + }, + "loginSaveConfirmation": { + "message": "$ITEMNAME$ saved to Bitwarden.", + "placeholders": { + "itemName": { + "content": "$1" + } + }, + "description": "Shown to user after item is saved." + }, + "loginUpdatedConfirmation": { + "message": "$ITEMNAME$ updated in Bitwarden.", + "placeholders": { + "itemName": { + "content": "$1" + } + }, + "description": "Shown to user after item is updated." }, "saveAsNewLoginAction": { "message": "Save as new login", @@ -1112,12 +1141,12 @@ "message": "Update login", "description": "Button text for updating an existing login entry." }, - "saveLoginPrompt": { - "message": "Save login?", + "saveLogin": { + "message": "Save login", "description": "Prompt asking the user if they want to save their login details." }, - "updateLoginPrompt": { - "message": "Update existing login?", + "updateLogin": { + "message": "Update existing login", "description": "Prompt asking the user if they want to update an existing login entry." }, "loginSaveSuccess": { @@ -1130,24 +1159,24 @@ }, "loginUpdateTaskSuccess": { "message": "Great job! You took the steps to make you and $ORGANIZATION$ more secure.", - "placeholders": { - "organization": { - "content": "$1" - } - }, - "description": "Shown to user after login is updated." + "placeholders": { + "organization": { + "content": "$1" + } + }, + "description": "Shown to user after login is updated." }, "loginUpdateTaskSuccessAdditional": { "message": "Thank you for making $ORGANIZATION$ more secure. You have $TASK_COUNT$ more passwords to update.", - "placeholders": { - "organization": { - "content": "$1" - }, - "task_count": { - "content": "$2" - } + "placeholders": { + "organization": { + "content": "$1" }, - "description": "Shown to user after login is updated." + "task_count": { + "content": "$2" + } + }, + "description": "Shown to user after login is updated." }, "nextSecurityTaskAction": { "message": "Change next password", @@ -2520,8 +2549,8 @@ "example": "Acme Corp" }, "count": { - "content": "$2", - "example": "2" + "content": "$2", + "example": "2" } } }, @@ -5199,6 +5228,12 @@ "changeAtRiskPassword": { "message": "Change at-risk password" }, + "settingsVaultOptions": { + "message": "Vault options" + }, + "emptyVaultDescription": { + "message": "The vault protects more than just your passwords. Store secure logins, IDs, cards and notes securely here." + }, "introCarouselLabel": { "message": "Welcome to Bitwarden" }, @@ -5225,5 +5260,50 @@ }, "secureDevicesBody": { "message": "Save unlimited passwords across unlimited devices with Bitwarden mobile, browser, and desktop apps." + }, + "emptyVaultNudgeTitle": { + "message": "Import existing passwords" + }, + "emptyVaultNudgeBody": { + "message": "Use the importer to quickly transfer logins to Bitwarden without manually adding them." + }, + "emptyVaultNudgeButton": { + "message": "Import now" + }, + "hasItemsVaultNudgeTitle": { + "message": "Welcome to your vault!" + }, + "hasItemsVaultNudgeBody": { + "message": "Autofill items for the current page\nFavorite items for easy access\nSearch your vault for something else" + }, + "newLoginNudgeTitle": { + "message": "Save time with autofill" + }, + "newLoginNudgeBody": { + "message": "Include a Website so this login appears as an autofill suggestion." + }, + "newCardNudgeTitle": { + "message": "Seamless online checkout" + }, + "newCardNudgeBody": { + "message": "With cards, easily autofill payment forms securely and accurately." + }, + "newIdentityNudgeTitle": { + "message": "Simplify creating accounts" + }, + "newIdentityNudgeBody": { + "message": "With identities, quickly autofill long registration or contact forms." + }, + "newNoteNudgeTitle": { + "message": "Keep your sensitive data safe" + }, + "newNoteNudgeBody": { + "message": "With notes, securely store sensitive data like banking or insurance details." + }, + "newSshNudgeTitle": { + "message": "Developer-friendly SSH access" + }, + "newSshNudgeBody": { + "message": "Store your keys and connect with the SSH agent for fast, encrypted authentication." } } diff --git a/apps/browser/src/_locales/en_GB/messages.json b/apps/browser/src/_locales/en_GB/messages.json index 520af1ca8ff..1e0def93584 100644 --- a/apps/browser/src/_locales/en_GB/messages.json +++ b/apps/browser/src/_locales/en_GB/messages.json @@ -2,6 +2,9 @@ "appName": { "message": "Bitwarden" }, + "appLogoLabel": { + "message": "Bitwarden logo" + }, "extName": { "message": "Bitwarden Password Manager", "description": "Extension name, MUST be less than 40 characters (Safari restriction)" @@ -883,6 +886,9 @@ "followTheStepsBelowToFinishLoggingIn": { "message": "Follow the steps below to finish logging in." }, + "followTheStepsBelowToFinishLoggingInWithSecurityKey": { + "message": "Follow the steps below to finish logging in with your security key." + }, "restartRegistration": { "message": "Restart registration" }, @@ -1056,23 +1062,49 @@ "notificationAddSave": { "message": "Save" }, - "loginSaveSuccessDetails": { - "message": "$USERNAME$ saved to Bitwarden.", + "notificationViewAria": { + "message": "View $ITEMNAME$, opens in new window", "placeholders": { - "username": { + "itemName": { "content": "$1" } }, - "description": "Shown to user after login is saved." + "description": "Aria label for the view button in notification bar confirmation message" }, - "loginUpdatedSuccessDetails": { - "message": "$USERNAME$ updated in Bitwarden.", + "notificationEditTooltip": { + "message": "Edit before saving", + "description": "Tooltip and Aria label for edit button on cipher item" + }, + "newNotification": { + "message": "New notification" + }, + "labelWithNotification": { + "message": "$LABEL$: New notification", + "description": "Label for the notification with a new login suggestion.", "placeholders": { - "username": { + "label": { + "content": "$1", + "example": "Login" + } + } + }, + "loginSaveConfirmation": { + "message": "$ITEMNAME$ saved to Bitwarden.", + "placeholders": { + "itemName": { "content": "$1" } }, - "description": "Shown to user after login is updated." + "description": "Shown to user after item is saved." + }, + "loginUpdatedConfirmation": { + "message": "$ITEMNAME$ updated in Bitwarden.", + "placeholders": { + "itemName": { + "content": "$1" + } + }, + "description": "Shown to user after item is updated." }, "saveAsNewLoginAction": { "message": "Save as new login", @@ -1082,12 +1114,12 @@ "message": "Update login", "description": "Button text for updating an existing login entry." }, - "saveLoginPrompt": { - "message": "Save login?", + "saveLogin": { + "message": "Save login", "description": "Prompt asking the user if they want to save their login details." }, - "updateLoginPrompt": { - "message": "Update existing login?", + "updateLogin": { + "message": "Update existing login", "description": "Prompt asking the user if they want to update an existing login entry." }, "loginSaveSuccess": { @@ -4928,8 +4960,8 @@ "message": "Password regenerated", "description": "Notification message for when a password has been regenerated" }, - "saveLoginToBitwarden": { - "message": "Save login to Bitwarden?", + "saveToBitwarden": { + "message": "Save to Bitwarden", "description": "Confirmation message for saving a login to Bitwarden" }, "spaceCharacterDescriptor": { @@ -5169,6 +5201,12 @@ "changeAtRiskPassword": { "message": "Change at-risk password" }, + "settingsVaultOptions": { + "message": "Vault options" + }, + "emptyVaultDescription": { + "message": "The vault protects more than just your passwords. Store secure logins, IDs, cards and notes securely here." + }, "introCarouselLabel": { "message": "Welcome to Bitwarden" }, @@ -5195,5 +5233,50 @@ }, "secureDevicesBody": { "message": "Save unlimited passwords across unlimited devices with Bitwarden mobile, browser, and desktop apps." + }, + "emptyVaultNudgeTitle": { + "message": "Import existing passwords" + }, + "emptyVaultNudgeBody": { + "message": "Use the importer to quickly transfer logins to Bitwarden without manually adding them." + }, + "emptyVaultNudgeButton": { + "message": "Import now" + }, + "hasItemsVaultNudgeTitle": { + "message": "Welcome to your vault!" + }, + "hasItemsVaultNudgeBody": { + "message": "Autofill items for the current page\nFavourite items for easy access\nSearch your vault for something else" + }, + "newLoginNudgeTitle": { + "message": "Save time with autofill" + }, + "newLoginNudgeBody": { + "message": "Include a Website so this login appears as an autofill suggestion." + }, + "newCardNudgeTitle": { + "message": "Seamless online checkout" + }, + "newCardNudgeBody": { + "message": "With cards, easily autofill payment forms securely and accurately." + }, + "newIdentityNudgeTitle": { + "message": "Simplify creating accounts" + }, + "newIdentityNudgeBody": { + "message": "With identities, quickly autofill long registration or contact forms." + }, + "newNoteNudgeTitle": { + "message": "Keep your sensitive data safe" + }, + "newNoteNudgeBody": { + "message": "With notes, securely store sensitive data like banking or insurance details." + }, + "newSshNudgeTitle": { + "message": "Developer-friendly SSH access" + }, + "newSshNudgeBody": { + "message": "Store your keys and connect with the SSH agent for fast, encrypted authentication." } } diff --git a/apps/browser/src/_locales/en_IN/messages.json b/apps/browser/src/_locales/en_IN/messages.json index f75664f10db..380eab31eda 100644 --- a/apps/browser/src/_locales/en_IN/messages.json +++ b/apps/browser/src/_locales/en_IN/messages.json @@ -2,6 +2,9 @@ "appName": { "message": "Bitwarden" }, + "appLogoLabel": { + "message": "Bitwarden logo" + }, "extName": { "message": "Bitwarden Password Manager", "description": "Extension name, MUST be less than 40 characters (Safari restriction)" @@ -883,6 +886,9 @@ "followTheStepsBelowToFinishLoggingIn": { "message": "Follow the steps below to finish logging in." }, + "followTheStepsBelowToFinishLoggingInWithSecurityKey": { + "message": "Follow the steps below to finish logging in with your security key." + }, "restartRegistration": { "message": "Restart registration" }, @@ -1056,23 +1062,49 @@ "notificationAddSave": { "message": "Yes, save now" }, - "loginSaveSuccessDetails": { - "message": "$USERNAME$ saved to Bitwarden.", + "notificationViewAria": { + "message": "View $ITEMNAME$, opens in new window", "placeholders": { - "username": { + "itemName": { "content": "$1" } }, - "description": "Shown to user after login is saved." + "description": "Aria label for the view button in notification bar confirmation message" }, - "loginUpdatedSuccessDetails": { - "message": "$USERNAME$ updated in Bitwarden.", + "notificationEditTooltip": { + "message": "Edit before saving", + "description": "Tooltip and Aria label for edit button on cipher item" + }, + "newNotification": { + "message": "New notification" + }, + "labelWithNotification": { + "message": "$LABEL$: New notification", + "description": "Label for the notification with a new login suggestion.", "placeholders": { - "username": { + "label": { + "content": "$1", + "example": "Login" + } + } + }, + "loginSaveConfirmation": { + "message": "$ITEMNAME$ saved to Bitwarden.", + "placeholders": { + "itemName": { "content": "$1" } }, - "description": "Shown to user after login is updated." + "description": "Shown to user after item is saved." + }, + "loginUpdatedConfirmation": { + "message": "$ITEMNAME$ updated in Bitwarden.", + "placeholders": { + "itemName": { + "content": "$1" + } + }, + "description": "Shown to user after item is updated." }, "saveAsNewLoginAction": { "message": "Save as new login", @@ -1082,12 +1114,12 @@ "message": "Update login", "description": "Button text for updating an existing login entry." }, - "saveLoginPrompt": { - "message": "Save login?", + "saveLogin": { + "message": "Save login", "description": "Prompt asking the user if they want to save their login details." }, - "updateLoginPrompt": { - "message": "Update existing login?", + "updateLogin": { + "message": "Update existing login", "description": "Prompt asking the user if they want to update an existing login entry." }, "loginSaveSuccess": { @@ -4928,8 +4960,8 @@ "message": "Password regenerated", "description": "Notification message for when a password has been regenerated" }, - "saveLoginToBitwarden": { - "message": "Save login to Bitwarden?", + "saveToBitwarden": { + "message": "Save to Bitwarden", "description": "Confirmation message for saving a login to Bitwarden" }, "spaceCharacterDescriptor": { @@ -5169,6 +5201,12 @@ "changeAtRiskPassword": { "message": "Change at-risk password" }, + "settingsVaultOptions": { + "message": "Vault options" + }, + "emptyVaultDescription": { + "message": "The vault protects more than just your passwords. Store secure logins, IDs, cards and notes securely here." + }, "introCarouselLabel": { "message": "Welcome to Bitwarden" }, @@ -5195,5 +5233,50 @@ }, "secureDevicesBody": { "message": "Save unlimited passwords across unlimited devices with Bitwarden mobile, browser, and desktop apps." + }, + "emptyVaultNudgeTitle": { + "message": "Import existing passwords" + }, + "emptyVaultNudgeBody": { + "message": "Use the importer to quickly transfer logins to Bitwarden without manually adding them." + }, + "emptyVaultNudgeButton": { + "message": "Import now" + }, + "hasItemsVaultNudgeTitle": { + "message": "Welcome to your vault!" + }, + "hasItemsVaultNudgeBody": { + "message": "Autofill items for the current page\nFavourite items for easy access\nSearch your vault for something else" + }, + "newLoginNudgeTitle": { + "message": "Save time with autofill" + }, + "newLoginNudgeBody": { + "message": "Include a Website so this login appears as an autofill suggestion." + }, + "newCardNudgeTitle": { + "message": "Seamless online checkout" + }, + "newCardNudgeBody": { + "message": "With cards, easily autofill payment forms securely and accurately." + }, + "newIdentityNudgeTitle": { + "message": "Simplify creating accounts" + }, + "newIdentityNudgeBody": { + "message": "With identities, quickly autofill long registration or contact forms." + }, + "newNoteNudgeTitle": { + "message": "Keep your sensitive data safe" + }, + "newNoteNudgeBody": { + "message": "With notes, securely store sensitive data like banking or insurance details." + }, + "newSshNudgeTitle": { + "message": "Developer-friendly SSH access" + }, + "newSshNudgeBody": { + "message": "Store your keys and connect with the SSH agent for fast, encrypted authentication." } } diff --git a/apps/browser/src/_locales/es/messages.json b/apps/browser/src/_locales/es/messages.json index 1400cb78845..76401c685ad 100644 --- a/apps/browser/src/_locales/es/messages.json +++ b/apps/browser/src/_locales/es/messages.json @@ -2,6 +2,9 @@ "appName": { "message": "Bitwarden" }, + "appLogoLabel": { + "message": "Bitwarden logo" + }, "extName": { "message": "Bitwarden - Administrador de contraseñas", "description": "Extension name, MUST be less than 40 characters (Safari restriction)" @@ -81,7 +84,7 @@ "message": "Pista de contraseña maestra (opcional)" }, "passwordStrengthScore": { - "message": "Password strength score $SCORE$", + "message": "Puntuación de fuerza de contraseña $SCORE$", "placeholders": { "score": { "content": "$1", @@ -380,7 +383,7 @@ "message": "Editar carpeta" }, "editFolderWithName": { - "message": "Edit folder: $FOLDERNAME$", + "message": "Editar carpeta: $FOLDERNAME$", "placeholders": { "foldername": { "content": "$1", @@ -872,7 +875,7 @@ "message": "Enter the code sent to your email" }, "enterTheCodeFromYourAuthenticatorApp": { - "message": "Enter the code from your authenticator app" + "message": "Introduce el código de tu aplicación de autenticación" }, "pressYourYubiKeyToAuthenticate": { "message": "Press your YubiKey to authenticate" @@ -883,6 +886,9 @@ "followTheStepsBelowToFinishLoggingIn": { "message": "Follow the steps below to finish logging in." }, + "followTheStepsBelowToFinishLoggingInWithSecurityKey": { + "message": "Follow the steps below to finish logging in with your security key." + }, "restartRegistration": { "message": "Reiniciar registro" }, @@ -1056,23 +1062,49 @@ "notificationAddSave": { "message": "Guardar" }, - "loginSaveSuccessDetails": { - "message": "$USERNAME$ saved to Bitwarden.", + "notificationViewAria": { + "message": "View $ITEMNAME$, opens in new window", "placeholders": { - "username": { + "itemName": { "content": "$1" } }, - "description": "Shown to user after login is saved." + "description": "Aria label for the view button in notification bar confirmation message" }, - "loginUpdatedSuccessDetails": { - "message": "$USERNAME$ updated in Bitwarden.", + "notificationEditTooltip": { + "message": "Edit before saving", + "description": "Tooltip and Aria label for edit button on cipher item" + }, + "newNotification": { + "message": "New notification" + }, + "labelWithNotification": { + "message": "$LABEL$: New notification", + "description": "Label for the notification with a new login suggestion.", "placeholders": { - "username": { + "label": { + "content": "$1", + "example": "Login" + } + } + }, + "loginSaveConfirmation": { + "message": "$ITEMNAME$ saved to Bitwarden.", + "placeholders": { + "itemName": { "content": "$1" } }, - "description": "Shown to user after login is updated." + "description": "Shown to user after item is saved." + }, + "loginUpdatedConfirmation": { + "message": "$ITEMNAME$ updated in Bitwarden.", + "placeholders": { + "itemName": { + "content": "$1" + } + }, + "description": "Shown to user after item is updated." }, "saveAsNewLoginAction": { "message": "Save as new login", @@ -1082,12 +1114,12 @@ "message": "Update login", "description": "Button text for updating an existing login entry." }, - "saveLoginPrompt": { - "message": "Save login?", + "saveLogin": { + "message": "Save login", "description": "Prompt asking the user if they want to save their login details." }, - "updateLoginPrompt": { - "message": "Update existing login?", + "updateLogin": { + "message": "Update existing login", "description": "Prompt asking the user if they want to update an existing login entry." }, "loginSaveSuccess": { @@ -1095,7 +1127,7 @@ "description": "Message displayed when login details are successfully saved." }, "loginUpdateSuccess": { - "message": "Login updated", + "message": "Inicio de sesión actualizado", "description": "Message displayed when login details are successfully updated." }, "loginUpdateTaskSuccess": { @@ -1124,7 +1156,7 @@ "description": "Message prompting user to undertake completion of another security task." }, "saveFailure": { - "message": "Error saving", + "message": "Error al guardar", "description": "Error message shown when the system fails to save login details." }, "saveFailureDetails": { @@ -1445,7 +1477,7 @@ "message": "Inserta tu llave de seguridad en el puerto USB de tu equipo. Si tiene un botón, púlsalo." }, "openInNewTab": { - "message": "Open in new tab" + "message": "Abrir en nueva pestaña" }, "webAuthnAuthenticate": { "message": "Autenticar WebAuthn" @@ -1663,7 +1695,7 @@ "message": "Arrastrar para ordenar" }, "dragToReorder": { - "message": "Drag to reorder" + "message": "Arrastra para reordenar" }, "cfTypeText": { "message": "Texto" @@ -2526,10 +2558,10 @@ "message": "Review at-risk logins" }, "reviewAtRiskPasswords": { - "message": "Review at-risk passwords" + "message": "Revisar contraseñas de riesgo" }, "reviewAtRiskLoginsSlideDesc": { - "message": "Your organization passwords are at-risk because they are weak, reused, and/or exposed.", + "message": "Las contraseñas de su organización están en riesgo porque son débiles, reutilizadas y/o expuestas.", "description": "Description of the review at-risk login slide on the at-risk password page carousel" }, "reviewAtRiskLoginSlideImgAltPeriod": { @@ -3614,10 +3646,10 @@ } }, "singleFieldNeedsAttention": { - "message": "1 field needs your attention." + "message": "1 campo necesita tu atención." }, "multipleFieldsNeedAttention": { - "message": "$COUNT$ fields need your attention.", + "message": "Los campos $COUNT$ necesitan tu atención.", "placeholders": { "count": { "content": "$1", @@ -4081,7 +4113,7 @@ "message": "Cuenta activa" }, "bitwardenAccount": { - "message": "Bitwarden account" + "message": "Cuenta de Bitwarden" }, "availableAccounts": { "message": "Cuentas disponibles" @@ -4179,7 +4211,7 @@ "description": "Notification message for when saving credentials has succeeded." }, "passwordSaved": { - "message": "Password saved!", + "message": "¡Contraseña guardada!", "description": "Notification message for when saving credentials has succeeded." }, "updateCipherAttemptSuccess": { @@ -4515,10 +4547,10 @@ "message": "Autofill options" }, "websiteUri": { - "message": "Website (URI)" + "message": "Página web (URI)" }, "websiteUriCount": { - "message": "Website (URI) $COUNT$", + "message": "Página web (URI) $COUNT$", "description": "Label for an input field that contains a website URI. The input field is part of a list of fields, and the count indicates the position of the field in the list.", "placeholders": { "count": { @@ -4589,7 +4621,7 @@ "message": "Activar animaciones" }, "showAnimations": { - "message": "Show animations" + "message": "Mostrar animaciones" }, "addAccount": { "message": "Añadir cuenta" @@ -4661,10 +4693,10 @@ "message": "Enter the the field's html id, name, aria-label, or placeholder." }, "editField": { - "message": "Edit field" + "message": "Editar campo" }, "editFieldLabel": { - "message": "Edit $LABEL$", + "message": "Editar $LABEL$", "placeholders": { "label": { "content": "$1", @@ -4828,16 +4860,16 @@ "message": "Enterprise policy requirements have been applied to this setting" }, "sshPrivateKey": { - "message": "Private key" + "message": "Clave privada" }, "sshPublicKey": { - "message": "Public key" + "message": "Clave pública" }, "sshFingerprint": { "message": "Fingerprint" }, "sshKeyAlgorithm": { - "message": "Key type" + "message": "Tipo de clave" }, "sshKeyAlgorithmED25519": { "message": "ED25519" @@ -4928,8 +4960,8 @@ "message": "Contraseña generada", "description": "Notification message for when a password has been regenerated" }, - "saveLoginToBitwarden": { - "message": "¿Guardar inicio de sesión en Bitwarden?", + "saveToBitwarden": { + "message": "Guardar en Bitwarden", "description": "Confirmation message for saving a login to Bitwarden" }, "spaceCharacterDescriptor": { @@ -5080,7 +5112,7 @@ "message": "Beta" }, "importantNotice": { - "message": "Important notice" + "message": "Aviso importante" }, "setupTwoStepLogin": { "message": "Set up two-step login" @@ -5092,10 +5124,10 @@ "message": "You can set up two-step login as an alternative way to protect your account or change your email to one you can access." }, "remindMeLater": { - "message": "Remind me later" + "message": "Recuérdame más tarde" }, "newDeviceVerificationNoticePageOneFormContent": { - "message": "Do you have reliable access to your email, $EMAIL$?", + "message": "¿Tienes acceso fiable a tu correo electrónico, $EMAIL$?", "placeholders": { "email": { "content": "$1", @@ -5104,7 +5136,7 @@ } }, "newDeviceVerificationNoticePageOneEmailAccessNo": { - "message": "No, I do not" + "message": "No, no lo tengo" }, "newDeviceVerificationNoticePageOneEmailAccessYes": { "message": "Sí, puedo acceder a mi correo electrónico de forma fiable" @@ -5125,19 +5157,19 @@ "message": "Extraancho" }, "sshKeyWrongPassword": { - "message": "The password you entered is incorrect." + "message": "La contraseña introducida es incorrecta." }, "importSshKey": { - "message": "Import" + "message": "Importar" }, "confirmSshKeyPassword": { - "message": "Confirm password" + "message": "Confirmar contraseña" }, "enterSshKeyPasswordDesc": { - "message": "Enter the password for the SSH key." + "message": "Introduce la contraseña para la clave SSH." }, "enterSshKeyPassword": { - "message": "Enter password" + "message": "Introduzca la contraseña" }, "invalidSshKey": { "message": "The SSH key is invalid" @@ -5167,7 +5199,13 @@ "message": "Para utilizar el desbloqueo biométrico, por favor actualice su aplicación de escritorio o desactive el desbloqueo de huella dactilar en los ajustes del escritorio." }, "changeAtRiskPassword": { - "message": "Change at-risk password" + "message": "Cambiar contraseña de riesgo" + }, + "settingsVaultOptions": { + "message": "Vault options" + }, + "emptyVaultDescription": { + "message": "The vault protects more than just your passwords. Store secure logins, IDs, cards and notes securely here." }, "introCarouselLabel": { "message": "Welcome to Bitwarden" @@ -5188,12 +5226,57 @@ "message": "Level up your logins" }, "secureUserBody": { - "message": "Use the generator to create and save strong, unique passwords for all your accounts." + "message": "Utilice el generador para crear y guardar contraseñas fuertes y únicas para todas sus cuentas." }, "secureDevices": { "message": "Your data, when and where you need it" }, "secureDevicesBody": { - "message": "Save unlimited passwords across unlimited devices with Bitwarden mobile, browser, and desktop apps." + "message": "Guarda contraseñas ilimitadas a través de dispositivos ilimitados con aplicaciones móviles, de navegador y de escritorio de Bitwarden." + }, + "emptyVaultNudgeTitle": { + "message": "Import existing passwords" + }, + "emptyVaultNudgeBody": { + "message": "Use the importer to quickly transfer logins to Bitwarden without manually adding them." + }, + "emptyVaultNudgeButton": { + "message": "Import now" + }, + "hasItemsVaultNudgeTitle": { + "message": "Welcome to your vault!" + }, + "hasItemsVaultNudgeBody": { + "message": "Autofill items for the current page\nFavorite items for easy access\nSearch your vault for something else" + }, + "newLoginNudgeTitle": { + "message": "Save time with autofill" + }, + "newLoginNudgeBody": { + "message": "Include a Website so this login appears as an autofill suggestion." + }, + "newCardNudgeTitle": { + "message": "Seamless online checkout" + }, + "newCardNudgeBody": { + "message": "With cards, easily autofill payment forms securely and accurately." + }, + "newIdentityNudgeTitle": { + "message": "Simplify creating accounts" + }, + "newIdentityNudgeBody": { + "message": "With identities, quickly autofill long registration or contact forms." + }, + "newNoteNudgeTitle": { + "message": "Keep your sensitive data safe" + }, + "newNoteNudgeBody": { + "message": "With notes, securely store sensitive data like banking or insurance details." + }, + "newSshNudgeTitle": { + "message": "Developer-friendly SSH access" + }, + "newSshNudgeBody": { + "message": "Store your keys and connect with the SSH agent for fast, encrypted authentication." } } diff --git a/apps/browser/src/_locales/et/messages.json b/apps/browser/src/_locales/et/messages.json index f9f7cb950fe..f76adcc27e2 100644 --- a/apps/browser/src/_locales/et/messages.json +++ b/apps/browser/src/_locales/et/messages.json @@ -2,6 +2,9 @@ "appName": { "message": "Bitwarden" }, + "appLogoLabel": { + "message": "Bitwarden logo" + }, "extName": { "message": "Bitwardeni paroolihaldur", "description": "Extension name, MUST be less than 40 characters (Safari restriction)" @@ -883,6 +886,9 @@ "followTheStepsBelowToFinishLoggingIn": { "message": "Follow the steps below to finish logging in." }, + "followTheStepsBelowToFinishLoggingInWithSecurityKey": { + "message": "Follow the steps below to finish logging in with your security key." + }, "restartRegistration": { "message": "Alusta registreerimist uuesti" }, @@ -1056,23 +1062,49 @@ "notificationAddSave": { "message": "Jah, salvesta see" }, - "loginSaveSuccessDetails": { - "message": "$USERNAME$ saved to Bitwarden.", + "notificationViewAria": { + "message": "View $ITEMNAME$, opens in new window", "placeholders": { - "username": { + "itemName": { "content": "$1" } }, - "description": "Shown to user after login is saved." + "description": "Aria label for the view button in notification bar confirmation message" }, - "loginUpdatedSuccessDetails": { - "message": "$USERNAME$ updated in Bitwarden.", + "notificationEditTooltip": { + "message": "Edit before saving", + "description": "Tooltip and Aria label for edit button on cipher item" + }, + "newNotification": { + "message": "New notification" + }, + "labelWithNotification": { + "message": "$LABEL$: New notification", + "description": "Label for the notification with a new login suggestion.", "placeholders": { - "username": { + "label": { + "content": "$1", + "example": "Login" + } + } + }, + "loginSaveConfirmation": { + "message": "$ITEMNAME$ saved to Bitwarden.", + "placeholders": { + "itemName": { "content": "$1" } }, - "description": "Shown to user after login is updated." + "description": "Shown to user after item is saved." + }, + "loginUpdatedConfirmation": { + "message": "$ITEMNAME$ updated in Bitwarden.", + "placeholders": { + "itemName": { + "content": "$1" + } + }, + "description": "Shown to user after item is updated." }, "saveAsNewLoginAction": { "message": "Save as new login", @@ -1082,12 +1114,12 @@ "message": "Update login", "description": "Button text for updating an existing login entry." }, - "saveLoginPrompt": { - "message": "Save login?", + "saveLogin": { + "message": "Save login", "description": "Prompt asking the user if they want to save their login details." }, - "updateLoginPrompt": { - "message": "Update existing login?", + "updateLogin": { + "message": "Update existing login", "description": "Prompt asking the user if they want to update an existing login entry." }, "loginSaveSuccess": { @@ -4928,8 +4960,8 @@ "message": "Password regenerated", "description": "Notification message for when a password has been regenerated" }, - "saveLoginToBitwarden": { - "message": "Save login to Bitwarden?", + "saveToBitwarden": { + "message": "Save to Bitwarden", "description": "Confirmation message for saving a login to Bitwarden" }, "spaceCharacterDescriptor": { @@ -5169,6 +5201,12 @@ "changeAtRiskPassword": { "message": "Change at-risk password" }, + "settingsVaultOptions": { + "message": "Vault options" + }, + "emptyVaultDescription": { + "message": "The vault protects more than just your passwords. Store secure logins, IDs, cards and notes securely here." + }, "introCarouselLabel": { "message": "Welcome to Bitwarden" }, @@ -5195,5 +5233,50 @@ }, "secureDevicesBody": { "message": "Save unlimited passwords across unlimited devices with Bitwarden mobile, browser, and desktop apps." + }, + "emptyVaultNudgeTitle": { + "message": "Import existing passwords" + }, + "emptyVaultNudgeBody": { + "message": "Use the importer to quickly transfer logins to Bitwarden without manually adding them." + }, + "emptyVaultNudgeButton": { + "message": "Import now" + }, + "hasItemsVaultNudgeTitle": { + "message": "Welcome to your vault!" + }, + "hasItemsVaultNudgeBody": { + "message": "Autofill items for the current page\nFavorite items for easy access\nSearch your vault for something else" + }, + "newLoginNudgeTitle": { + "message": "Save time with autofill" + }, + "newLoginNudgeBody": { + "message": "Include a Website so this login appears as an autofill suggestion." + }, + "newCardNudgeTitle": { + "message": "Seamless online checkout" + }, + "newCardNudgeBody": { + "message": "With cards, easily autofill payment forms securely and accurately." + }, + "newIdentityNudgeTitle": { + "message": "Simplify creating accounts" + }, + "newIdentityNudgeBody": { + "message": "With identities, quickly autofill long registration or contact forms." + }, + "newNoteNudgeTitle": { + "message": "Keep your sensitive data safe" + }, + "newNoteNudgeBody": { + "message": "With notes, securely store sensitive data like banking or insurance details." + }, + "newSshNudgeTitle": { + "message": "Developer-friendly SSH access" + }, + "newSshNudgeBody": { + "message": "Store your keys and connect with the SSH agent for fast, encrypted authentication." } } diff --git a/apps/browser/src/_locales/eu/messages.json b/apps/browser/src/_locales/eu/messages.json index ec1f5189946..cd3d58896d4 100644 --- a/apps/browser/src/_locales/eu/messages.json +++ b/apps/browser/src/_locales/eu/messages.json @@ -2,6 +2,9 @@ "appName": { "message": "Bitwarden" }, + "appLogoLabel": { + "message": "Bitwarden logo" + }, "extName": { "message": "Bitwarden Password Manager", "description": "Extension name, MUST be less than 40 characters (Safari restriction)" @@ -883,6 +886,9 @@ "followTheStepsBelowToFinishLoggingIn": { "message": "Follow the steps below to finish logging in." }, + "followTheStepsBelowToFinishLoggingInWithSecurityKey": { + "message": "Follow the steps below to finish logging in with your security key." + }, "restartRegistration": { "message": "Restart registration" }, @@ -1056,23 +1062,49 @@ "notificationAddSave": { "message": "Gorde" }, - "loginSaveSuccessDetails": { - "message": "$USERNAME$ saved to Bitwarden.", + "notificationViewAria": { + "message": "View $ITEMNAME$, opens in new window", "placeholders": { - "username": { + "itemName": { "content": "$1" } }, - "description": "Shown to user after login is saved." + "description": "Aria label for the view button in notification bar confirmation message" }, - "loginUpdatedSuccessDetails": { - "message": "$USERNAME$ updated in Bitwarden.", + "notificationEditTooltip": { + "message": "Edit before saving", + "description": "Tooltip and Aria label for edit button on cipher item" + }, + "newNotification": { + "message": "New notification" + }, + "labelWithNotification": { + "message": "$LABEL$: New notification", + "description": "Label for the notification with a new login suggestion.", "placeholders": { - "username": { + "label": { + "content": "$1", + "example": "Login" + } + } + }, + "loginSaveConfirmation": { + "message": "$ITEMNAME$ saved to Bitwarden.", + "placeholders": { + "itemName": { "content": "$1" } }, - "description": "Shown to user after login is updated." + "description": "Shown to user after item is saved." + }, + "loginUpdatedConfirmation": { + "message": "$ITEMNAME$ updated in Bitwarden.", + "placeholders": { + "itemName": { + "content": "$1" + } + }, + "description": "Shown to user after item is updated." }, "saveAsNewLoginAction": { "message": "Save as new login", @@ -1082,12 +1114,12 @@ "message": "Update login", "description": "Button text for updating an existing login entry." }, - "saveLoginPrompt": { - "message": "Save login?", + "saveLogin": { + "message": "Save login", "description": "Prompt asking the user if they want to save their login details." }, - "updateLoginPrompt": { - "message": "Update existing login?", + "updateLogin": { + "message": "Update existing login", "description": "Prompt asking the user if they want to update an existing login entry." }, "loginSaveSuccess": { @@ -4928,8 +4960,8 @@ "message": "Password regenerated", "description": "Notification message for when a password has been regenerated" }, - "saveLoginToBitwarden": { - "message": "Save login to Bitwarden?", + "saveToBitwarden": { + "message": "Save to Bitwarden", "description": "Confirmation message for saving a login to Bitwarden" }, "spaceCharacterDescriptor": { @@ -5169,6 +5201,12 @@ "changeAtRiskPassword": { "message": "Change at-risk password" }, + "settingsVaultOptions": { + "message": "Vault options" + }, + "emptyVaultDescription": { + "message": "The vault protects more than just your passwords. Store secure logins, IDs, cards and notes securely here." + }, "introCarouselLabel": { "message": "Welcome to Bitwarden" }, @@ -5195,5 +5233,50 @@ }, "secureDevicesBody": { "message": "Save unlimited passwords across unlimited devices with Bitwarden mobile, browser, and desktop apps." + }, + "emptyVaultNudgeTitle": { + "message": "Import existing passwords" + }, + "emptyVaultNudgeBody": { + "message": "Use the importer to quickly transfer logins to Bitwarden without manually adding them." + }, + "emptyVaultNudgeButton": { + "message": "Import now" + }, + "hasItemsVaultNudgeTitle": { + "message": "Welcome to your vault!" + }, + "hasItemsVaultNudgeBody": { + "message": "Autofill items for the current page\nFavorite items for easy access\nSearch your vault for something else" + }, + "newLoginNudgeTitle": { + "message": "Save time with autofill" + }, + "newLoginNudgeBody": { + "message": "Include a Website so this login appears as an autofill suggestion." + }, + "newCardNudgeTitle": { + "message": "Seamless online checkout" + }, + "newCardNudgeBody": { + "message": "With cards, easily autofill payment forms securely and accurately." + }, + "newIdentityNudgeTitle": { + "message": "Simplify creating accounts" + }, + "newIdentityNudgeBody": { + "message": "With identities, quickly autofill long registration or contact forms." + }, + "newNoteNudgeTitle": { + "message": "Keep your sensitive data safe" + }, + "newNoteNudgeBody": { + "message": "With notes, securely store sensitive data like banking or insurance details." + }, + "newSshNudgeTitle": { + "message": "Developer-friendly SSH access" + }, + "newSshNudgeBody": { + "message": "Store your keys and connect with the SSH agent for fast, encrypted authentication." } } diff --git a/apps/browser/src/_locales/fa/messages.json b/apps/browser/src/_locales/fa/messages.json index 0e5e8943b7d..b8f8d2ccdbf 100644 --- a/apps/browser/src/_locales/fa/messages.json +++ b/apps/browser/src/_locales/fa/messages.json @@ -2,6 +2,9 @@ "appName": { "message": "Bitwarden" }, + "appLogoLabel": { + "message": "Bitwarden logo" + }, "extName": { "message": "مدیریت رمز عبور Bitwarden", "description": "Extension name, MUST be less than 40 characters (Safari restriction)" @@ -883,6 +886,9 @@ "followTheStepsBelowToFinishLoggingIn": { "message": "Follow the steps below to finish logging in." }, + "followTheStepsBelowToFinishLoggingInWithSecurityKey": { + "message": "Follow the steps below to finish logging in with your security key." + }, "restartRegistration": { "message": "Restart registration" }, @@ -1056,23 +1062,49 @@ "notificationAddSave": { "message": "ذخیره" }, - "loginSaveSuccessDetails": { - "message": "$USERNAME$ saved to Bitwarden.", + "notificationViewAria": { + "message": "View $ITEMNAME$, opens in new window", "placeholders": { - "username": { + "itemName": { "content": "$1" } }, - "description": "Shown to user after login is saved." + "description": "Aria label for the view button in notification bar confirmation message" }, - "loginUpdatedSuccessDetails": { - "message": "$USERNAME$ updated in Bitwarden.", + "notificationEditTooltip": { + "message": "Edit before saving", + "description": "Tooltip and Aria label for edit button on cipher item" + }, + "newNotification": { + "message": "New notification" + }, + "labelWithNotification": { + "message": "$LABEL$: New notification", + "description": "Label for the notification with a new login suggestion.", "placeholders": { - "username": { + "label": { + "content": "$1", + "example": "Login" + } + } + }, + "loginSaveConfirmation": { + "message": "$ITEMNAME$ saved to Bitwarden.", + "placeholders": { + "itemName": { "content": "$1" } }, - "description": "Shown to user after login is updated." + "description": "Shown to user after item is saved." + }, + "loginUpdatedConfirmation": { + "message": "$ITEMNAME$ updated in Bitwarden.", + "placeholders": { + "itemName": { + "content": "$1" + } + }, + "description": "Shown to user after item is updated." }, "saveAsNewLoginAction": { "message": "Save as new login", @@ -1082,12 +1114,12 @@ "message": "Update login", "description": "Button text for updating an existing login entry." }, - "saveLoginPrompt": { - "message": "Save login?", + "saveLogin": { + "message": "Save login", "description": "Prompt asking the user if they want to save their login details." }, - "updateLoginPrompt": { - "message": "Update existing login?", + "updateLogin": { + "message": "Update existing login", "description": "Prompt asking the user if they want to update an existing login entry." }, "loginSaveSuccess": { @@ -4928,8 +4960,8 @@ "message": "Password regenerated", "description": "Notification message for when a password has been regenerated" }, - "saveLoginToBitwarden": { - "message": "Save login to Bitwarden?", + "saveToBitwarden": { + "message": "Save to Bitwarden", "description": "Confirmation message for saving a login to Bitwarden" }, "spaceCharacterDescriptor": { @@ -5169,6 +5201,12 @@ "changeAtRiskPassword": { "message": "Change at-risk password" }, + "settingsVaultOptions": { + "message": "Vault options" + }, + "emptyVaultDescription": { + "message": "The vault protects more than just your passwords. Store secure logins, IDs, cards and notes securely here." + }, "introCarouselLabel": { "message": "Welcome to Bitwarden" }, @@ -5195,5 +5233,50 @@ }, "secureDevicesBody": { "message": "Save unlimited passwords across unlimited devices with Bitwarden mobile, browser, and desktop apps." + }, + "emptyVaultNudgeTitle": { + "message": "Import existing passwords" + }, + "emptyVaultNudgeBody": { + "message": "Use the importer to quickly transfer logins to Bitwarden without manually adding them." + }, + "emptyVaultNudgeButton": { + "message": "Import now" + }, + "hasItemsVaultNudgeTitle": { + "message": "Welcome to your vault!" + }, + "hasItemsVaultNudgeBody": { + "message": "Autofill items for the current page\nFavorite items for easy access\nSearch your vault for something else" + }, + "newLoginNudgeTitle": { + "message": "Save time with autofill" + }, + "newLoginNudgeBody": { + "message": "Include a Website so this login appears as an autofill suggestion." + }, + "newCardNudgeTitle": { + "message": "Seamless online checkout" + }, + "newCardNudgeBody": { + "message": "With cards, easily autofill payment forms securely and accurately." + }, + "newIdentityNudgeTitle": { + "message": "Simplify creating accounts" + }, + "newIdentityNudgeBody": { + "message": "With identities, quickly autofill long registration or contact forms." + }, + "newNoteNudgeTitle": { + "message": "Keep your sensitive data safe" + }, + "newNoteNudgeBody": { + "message": "With notes, securely store sensitive data like banking or insurance details." + }, + "newSshNudgeTitle": { + "message": "Developer-friendly SSH access" + }, + "newSshNudgeBody": { + "message": "Store your keys and connect with the SSH agent for fast, encrypted authentication." } } diff --git a/apps/browser/src/_locales/fi/messages.json b/apps/browser/src/_locales/fi/messages.json index 4048925ebaa..c3f48b1df3b 100644 --- a/apps/browser/src/_locales/fi/messages.json +++ b/apps/browser/src/_locales/fi/messages.json @@ -2,6 +2,9 @@ "appName": { "message": "Bitwarden" }, + "appLogoLabel": { + "message": "Bitwarden-logo" + }, "extName": { "message": "Bitwarden Salasanahallinta", "description": "Extension name, MUST be less than 40 characters (Safari restriction)" @@ -883,6 +886,9 @@ "followTheStepsBelowToFinishLoggingIn": { "message": "Viimeistele kirjautuminen seuraamalla seuraavia vaiheita." }, + "followTheStepsBelowToFinishLoggingInWithSecurityKey": { + "message": "Follow the steps below to finish logging in with your security key." + }, "restartRegistration": { "message": "Aloita rekisteröityminen alusta" }, @@ -1056,23 +1062,49 @@ "notificationAddSave": { "message": "Tallenna" }, - "loginSaveSuccessDetails": { - "message": "$USERNAME$ tallennettiin Bitwardeniin.", + "notificationViewAria": { + "message": "View $ITEMNAME$, opens in new window", "placeholders": { - "username": { + "itemName": { "content": "$1" } }, - "description": "Shown to user after login is saved." + "description": "Aria label for the view button in notification bar confirmation message" }, - "loginUpdatedSuccessDetails": { - "message": "$USERNAME$ päivitettiin Bitwardeniin.", + "notificationEditTooltip": { + "message": "Muokkaa ennen tallentamista", + "description": "Tooltip and Aria label for edit button on cipher item" + }, + "newNotification": { + "message": "Uusi ilmoitus" + }, + "labelWithNotification": { + "message": "$LABEL$: Uusi ilmoitus", + "description": "Label for the notification with a new login suggestion.", "placeholders": { - "username": { + "label": { + "content": "$1", + "example": "Login" + } + } + }, + "loginSaveConfirmation": { + "message": "$ITEMNAME$ tallennettiin Bitwardeniin.", + "placeholders": { + "itemName": { "content": "$1" } }, - "description": "Shown to user after login is updated." + "description": "Shown to user after item is saved." + }, + "loginUpdatedConfirmation": { + "message": "$ITEMNAME$ päivitettiin Bitwardeniin.", + "placeholders": { + "itemName": { + "content": "$1" + } + }, + "description": "Shown to user after item is updated." }, "saveAsNewLoginAction": { "message": "Tallenna uutena kirjautumistietona", @@ -1082,12 +1114,12 @@ "message": "Päivitä kirjautumistieto", "description": "Button text for updating an existing login entry." }, - "saveLoginPrompt": { - "message": "Tallennetaanko kirjautumistieto?", + "saveLogin": { + "message": "Tallenna kirjautumistieto", "description": "Prompt asking the user if they want to save their login details." }, - "updateLoginPrompt": { - "message": "Päivitetäänkö olemassaoleva kirjautumistieto?", + "updateLogin": { + "message": "Päivitä olemassaoleva kirjautumistieto", "description": "Prompt asking the user if they want to update an existing login entry." }, "loginSaveSuccess": { @@ -1099,7 +1131,7 @@ "description": "Message displayed when login details are successfully updated." }, "loginUpdateTaskSuccess": { - "message": "Great job! You took the steps to make you and $ORGANIZATION$ more secure.", + "message": "Hienoa työtä! Otit askeleita, joilla teet itsestäsi ja organisaatiosta $ORGANIZATION$ turvallisemman.", "placeholders": { "organization": { "content": "$1" @@ -1108,7 +1140,7 @@ "description": "Shown to user after login is updated." }, "loginUpdateTaskSuccessAdditional": { - "message": "Thank you for making $ORGANIZATION$ more secure. You have $TASK_COUNT$ more passwords to update.", + "message": "Kiitos, että teet organisaatiosta $ORGANIZATION$ turvallisemman. Sinulla on vielä $TASK_COUNT$ salasanaa päivitettävänä.", "placeholders": { "organization": { "content": "$1" @@ -1120,7 +1152,7 @@ "description": "Shown to user after login is updated." }, "nextSecurityTaskAction": { - "message": "Change next password", + "message": "Vaihda seuraava salasana", "description": "Message prompting user to undertake completion of another security task." }, "saveFailure": { @@ -2323,7 +2355,7 @@ "message": "Tietosuojakäytäntö" }, "yourNewPasswordCannotBeTheSameAsYourCurrentPassword": { - "message": "Your new password cannot be the same as your current password." + "message": "Uusi salasanasi ei voi olla sama kuin nykyinen salasanasi." }, "hintEqualsPassword": { "message": "Salasanavihjeesi ei voi olla sama kuin salasanasi." @@ -2533,14 +2565,14 @@ "description": "Description of the review at-risk login slide on the at-risk password page carousel" }, "reviewAtRiskLoginSlideImgAltPeriod": { - "message": "Illustration of a list of logins that are at-risk." + "message": "Kuvitus vaarantuneiden kirjautumistietojen luettelosta." }, "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" }, "generatePasswordSlideImgAltPeriod": { - "message": "Illustration of the Bitwarden autofill menu displaying a generated password." + "message": "Kuvitus Bitwardenin automaattitäytön valikosta, luodulla salasanalla." }, "updateInBitwarden": { "message": "Päivitä Bitwardenissa" @@ -2550,7 +2582,7 @@ "description": "Description of the update in Bitwarden slide on the at-risk password page carousel" }, "updateInBitwardenSlideImgAltPeriod": { - "message": "Illustration of a Bitwarden’s notification prompting the user to update the login." + "message": "Kuvitus ilmoituksesta, jossa Bitwarden tarjoaa kirjautumistiedon päivitystä." }, "turnOnAutofill": { "message": "Ota automaattitäyttö käyttöön" @@ -3006,7 +3038,7 @@ } }, "exportingIndividualVaultWithAttachmentsDescription": { - "message": "Only the individual vault items including attachments associated with $EMAIL$ will be exported. Organization vault items will not be included", + "message": "Vain henkilökohtaiset tunnukseen $EMAIL$ liittyvät holvin kohteet liitetiedostoineen viedään. Organisaation holvikohteita ei sisällytetä", "placeholders": { "email": { "content": "$1", @@ -4272,7 +4304,7 @@ } }, "viewItemTitleWithField": { - "message": "View item - $ITEMNAME$ - $FIELD$", + "message": "Tarkastele kohdetta - $ITEMNAME$ - $FIELD$", "description": "Title for a link that opens a view for an item.", "placeholders": { "itemname": { @@ -4296,7 +4328,7 @@ } }, "autofillTitleWithField": { - "message": "Autofill - $ITEMNAME$ - $FIELD$", + "message": "Automaattitäytä - $ITEMNAME$ - $FIELD$", "description": "Title for a button that autofills a login item.", "placeholders": { "itemname": { @@ -4928,8 +4960,8 @@ "message": "Salasana luotiin uudelleen", "description": "Notification message for when a password has been regenerated" }, - "saveLoginToBitwarden": { - "message": "Tallennetaanko kirjautumistieto Bitwardeniin?", + "saveToBitwarden": { + "message": "Tallenna Bitwardeniin", "description": "Confirmation message for saving a login to Bitwarden" }, "spaceCharacterDescriptor": { @@ -5169,31 +5201,82 @@ "changeAtRiskPassword": { "message": "Vaihda vaarantunut salasana" }, + "settingsVaultOptions": { + "message": "Vault options" + }, + "emptyVaultDescription": { + "message": "The vault protects more than just your passwords. Store secure logins, IDs, cards and notes securely here." + }, "introCarouselLabel": { - "message": "Welcome to Bitwarden" + "message": "Tervetuloa Bitwardeniin" }, "securityPrioritized": { - "message": "Security, prioritized" + "message": "Tietoturva etusijalla" }, "securityPrioritizedBody": { - "message": "Save logins, cards, and identities to your secure vault. Bitwarden uses zero-knowledge, end-to-end encryption to protect what’s important to you." + "message": "Tallenna kirjautumistiedot, kortit ja henkilöllisyydet suojattuun holviisi. Bitwarden suojaa tärkeät tietosi nollatietoisella päästä päähän -salauksella." }, "quickLogin": { - "message": "Quick and easy login" + "message": "Nopea ja helppo kirjautuminen" }, "quickLoginBody": { - "message": "Set up biometric unlock and autofill to log into your accounts without typing a single letter." + "message": "Määritä biometrinen avaus ja automaattitäyttö kirjautuaksesi tileillesi kirjoittamatta yhtäkään kirjainta." }, "secureUser": { - "message": "Level up your logins" + "message": "Nosta kirjautumisesi uudelle tasolle" }, "secureUserBody": { - "message": "Use the generator to create and save strong, unique passwords for all your accounts." + "message": "Käytä generaattoria luodaksesi ja tallentaaksesi vahvoja, ainutlaatuisia salasanoja kaikille tileillesi." }, "secureDevices": { - "message": "Your data, when and where you need it" + "message": "Sinun tietosi, missä ja milloin tarvitset niitä" }, "secureDevicesBody": { - "message": "Save unlimited passwords across unlimited devices with Bitwarden mobile, browser, and desktop apps." + "message": "Tallenna rajattomasti salasanoja, rajattomalla määrällä laitteita, Bitwardenin mobiili-, selain- ja työpöytäsovelluksilla." + }, + "emptyVaultNudgeTitle": { + "message": "Import existing passwords" + }, + "emptyVaultNudgeBody": { + "message": "Use the importer to quickly transfer logins to Bitwarden without manually adding them." + }, + "emptyVaultNudgeButton": { + "message": "Import now" + }, + "hasItemsVaultNudgeTitle": { + "message": "Welcome to your vault!" + }, + "hasItemsVaultNudgeBody": { + "message": "Autofill items for the current page\nFavorite items for easy access\nSearch your vault for something else" + }, + "newLoginNudgeTitle": { + "message": "Save time with autofill" + }, + "newLoginNudgeBody": { + "message": "Include a Website so this login appears as an autofill suggestion." + }, + "newCardNudgeTitle": { + "message": "Seamless online checkout" + }, + "newCardNudgeBody": { + "message": "With cards, easily autofill payment forms securely and accurately." + }, + "newIdentityNudgeTitle": { + "message": "Simplify creating accounts" + }, + "newIdentityNudgeBody": { + "message": "With identities, quickly autofill long registration or contact forms." + }, + "newNoteNudgeTitle": { + "message": "Keep your sensitive data safe" + }, + "newNoteNudgeBody": { + "message": "With notes, securely store sensitive data like banking or insurance details." + }, + "newSshNudgeTitle": { + "message": "Developer-friendly SSH access" + }, + "newSshNudgeBody": { + "message": "Store your keys and connect with the SSH agent for fast, encrypted authentication." } } diff --git a/apps/browser/src/_locales/fil/messages.json b/apps/browser/src/_locales/fil/messages.json index 9c3865bc0da..d01670a2353 100644 --- a/apps/browser/src/_locales/fil/messages.json +++ b/apps/browser/src/_locales/fil/messages.json @@ -2,6 +2,9 @@ "appName": { "message": "Bitwarden" }, + "appLogoLabel": { + "message": "Bitwarden logo" + }, "extName": { "message": "Bitwarden Password Manager", "description": "Extension name, MUST be less than 40 characters (Safari restriction)" @@ -883,6 +886,9 @@ "followTheStepsBelowToFinishLoggingIn": { "message": "Follow the steps below to finish logging in." }, + "followTheStepsBelowToFinishLoggingInWithSecurityKey": { + "message": "Follow the steps below to finish logging in with your security key." + }, "restartRegistration": { "message": "Restart registration" }, @@ -1056,23 +1062,49 @@ "notificationAddSave": { "message": "I-save" }, - "loginSaveSuccessDetails": { - "message": "$USERNAME$ saved to Bitwarden.", + "notificationViewAria": { + "message": "View $ITEMNAME$, opens in new window", "placeholders": { - "username": { + "itemName": { "content": "$1" } }, - "description": "Shown to user after login is saved." + "description": "Aria label for the view button in notification bar confirmation message" }, - "loginUpdatedSuccessDetails": { - "message": "$USERNAME$ updated in Bitwarden.", + "notificationEditTooltip": { + "message": "Edit before saving", + "description": "Tooltip and Aria label for edit button on cipher item" + }, + "newNotification": { + "message": "New notification" + }, + "labelWithNotification": { + "message": "$LABEL$: New notification", + "description": "Label for the notification with a new login suggestion.", "placeholders": { - "username": { + "label": { + "content": "$1", + "example": "Login" + } + } + }, + "loginSaveConfirmation": { + "message": "$ITEMNAME$ saved to Bitwarden.", + "placeholders": { + "itemName": { "content": "$1" } }, - "description": "Shown to user after login is updated." + "description": "Shown to user after item is saved." + }, + "loginUpdatedConfirmation": { + "message": "$ITEMNAME$ updated in Bitwarden.", + "placeholders": { + "itemName": { + "content": "$1" + } + }, + "description": "Shown to user after item is updated." }, "saveAsNewLoginAction": { "message": "Save as new login", @@ -1082,12 +1114,12 @@ "message": "Update login", "description": "Button text for updating an existing login entry." }, - "saveLoginPrompt": { - "message": "Save login?", + "saveLogin": { + "message": "Save login", "description": "Prompt asking the user if they want to save their login details." }, - "updateLoginPrompt": { - "message": "Update existing login?", + "updateLogin": { + "message": "Update existing login", "description": "Prompt asking the user if they want to update an existing login entry." }, "loginSaveSuccess": { @@ -4928,8 +4960,8 @@ "message": "Password regenerated", "description": "Notification message for when a password has been regenerated" }, - "saveLoginToBitwarden": { - "message": "Save login to Bitwarden?", + "saveToBitwarden": { + "message": "Save to Bitwarden", "description": "Confirmation message for saving a login to Bitwarden" }, "spaceCharacterDescriptor": { @@ -5169,6 +5201,12 @@ "changeAtRiskPassword": { "message": "Change at-risk password" }, + "settingsVaultOptions": { + "message": "Vault options" + }, + "emptyVaultDescription": { + "message": "The vault protects more than just your passwords. Store secure logins, IDs, cards and notes securely here." + }, "introCarouselLabel": { "message": "Welcome to Bitwarden" }, @@ -5195,5 +5233,50 @@ }, "secureDevicesBody": { "message": "Save unlimited passwords across unlimited devices with Bitwarden mobile, browser, and desktop apps." + }, + "emptyVaultNudgeTitle": { + "message": "Import existing passwords" + }, + "emptyVaultNudgeBody": { + "message": "Use the importer to quickly transfer logins to Bitwarden without manually adding them." + }, + "emptyVaultNudgeButton": { + "message": "Import now" + }, + "hasItemsVaultNudgeTitle": { + "message": "Welcome to your vault!" + }, + "hasItemsVaultNudgeBody": { + "message": "Autofill items for the current page\nFavorite items for easy access\nSearch your vault for something else" + }, + "newLoginNudgeTitle": { + "message": "Save time with autofill" + }, + "newLoginNudgeBody": { + "message": "Include a Website so this login appears as an autofill suggestion." + }, + "newCardNudgeTitle": { + "message": "Seamless online checkout" + }, + "newCardNudgeBody": { + "message": "With cards, easily autofill payment forms securely and accurately." + }, + "newIdentityNudgeTitle": { + "message": "Simplify creating accounts" + }, + "newIdentityNudgeBody": { + "message": "With identities, quickly autofill long registration or contact forms." + }, + "newNoteNudgeTitle": { + "message": "Keep your sensitive data safe" + }, + "newNoteNudgeBody": { + "message": "With notes, securely store sensitive data like banking or insurance details." + }, + "newSshNudgeTitle": { + "message": "Developer-friendly SSH access" + }, + "newSshNudgeBody": { + "message": "Store your keys and connect with the SSH agent for fast, encrypted authentication." } } diff --git a/apps/browser/src/_locales/fr/messages.json b/apps/browser/src/_locales/fr/messages.json index 1f05f699ea9..b8c4fa9d011 100644 --- a/apps/browser/src/_locales/fr/messages.json +++ b/apps/browser/src/_locales/fr/messages.json @@ -2,6 +2,9 @@ "appName": { "message": "Bitwarden" }, + "appLogoLabel": { + "message": "Bitwarden logo" + }, "extName": { "message": "Gestionnaire de mots de passe Bitwarden", "description": "Extension name, MUST be less than 40 characters (Safari restriction)" @@ -883,6 +886,9 @@ "followTheStepsBelowToFinishLoggingIn": { "message": "Suivez les étapes ci-dessous afin de réussir à vous connecter." }, + "followTheStepsBelowToFinishLoggingInWithSecurityKey": { + "message": "Follow the steps below to finish logging in with your security key." + }, "restartRegistration": { "message": "Redémarrer l'inscription" }, @@ -1056,23 +1062,49 @@ "notificationAddSave": { "message": "Enregistrer" }, - "loginSaveSuccessDetails": { - "message": "$USERNAME$ enregistré dans Bitwarden.", + "notificationViewAria": { + "message": "View $ITEMNAME$, opens in new window", "placeholders": { - "username": { + "itemName": { "content": "$1" } }, - "description": "Shown to user after login is saved." + "description": "Aria label for the view button in notification bar confirmation message" }, - "loginUpdatedSuccessDetails": { - "message": "$USERNAME$ mis à jour dans Bitwarden.", + "notificationEditTooltip": { + "message": "Edit before saving", + "description": "Tooltip and Aria label for edit button on cipher item" + }, + "newNotification": { + "message": "New notification" + }, + "labelWithNotification": { + "message": "$LABEL$: New notification", + "description": "Label for the notification with a new login suggestion.", "placeholders": { - "username": { + "label": { + "content": "$1", + "example": "Login" + } + } + }, + "loginSaveConfirmation": { + "message": "$ITEMNAME$ saved to Bitwarden.", + "placeholders": { + "itemName": { "content": "$1" } }, - "description": "Shown to user after login is updated." + "description": "Shown to user after item is saved." + }, + "loginUpdatedConfirmation": { + "message": "$ITEMNAME$ updated in Bitwarden.", + "placeholders": { + "itemName": { + "content": "$1" + } + }, + "description": "Shown to user after item is updated." }, "saveAsNewLoginAction": { "message": "Enregistrer en tant que nouvel identifiant", @@ -1082,12 +1114,12 @@ "message": "Mettre à jour l'identifiant", "description": "Button text for updating an existing login entry." }, - "saveLoginPrompt": { - "message": "Enregistrer l'identifiant ?", + "saveLogin": { + "message": "Save login", "description": "Prompt asking the user if they want to save their login details." }, - "updateLoginPrompt": { - "message": "Mettre à jour de l'identifiant existant ?", + "updateLogin": { + "message": "Update existing login", "description": "Prompt asking the user if they want to update an existing login entry." }, "loginSaveSuccess": { @@ -4928,8 +4960,8 @@ "message": "Mot de passe régénéré", "description": "Notification message for when a password has been regenerated" }, - "saveLoginToBitwarden": { - "message": "Enregistrer l'identifiant sur Bitwarden ?", + "saveToBitwarden": { + "message": "Save to Bitwarden", "description": "Confirmation message for saving a login to Bitwarden" }, "spaceCharacterDescriptor": { @@ -5169,6 +5201,12 @@ "changeAtRiskPassword": { "message": "Changer le mot de passe à risque" }, + "settingsVaultOptions": { + "message": "Vault options" + }, + "emptyVaultDescription": { + "message": "The vault protects more than just your passwords. Store secure logins, IDs, cards and notes securely here." + }, "introCarouselLabel": { "message": "Welcome to Bitwarden" }, @@ -5195,5 +5233,50 @@ }, "secureDevicesBody": { "message": "Save unlimited passwords across unlimited devices with Bitwarden mobile, browser, and desktop apps." + }, + "emptyVaultNudgeTitle": { + "message": "Import existing passwords" + }, + "emptyVaultNudgeBody": { + "message": "Use the importer to quickly transfer logins to Bitwarden without manually adding them." + }, + "emptyVaultNudgeButton": { + "message": "Import now" + }, + "hasItemsVaultNudgeTitle": { + "message": "Welcome to your vault!" + }, + "hasItemsVaultNudgeBody": { + "message": "Autofill items for the current page\nFavorite items for easy access\nSearch your vault for something else" + }, + "newLoginNudgeTitle": { + "message": "Save time with autofill" + }, + "newLoginNudgeBody": { + "message": "Include a Website so this login appears as an autofill suggestion." + }, + "newCardNudgeTitle": { + "message": "Seamless online checkout" + }, + "newCardNudgeBody": { + "message": "With cards, easily autofill payment forms securely and accurately." + }, + "newIdentityNudgeTitle": { + "message": "Simplify creating accounts" + }, + "newIdentityNudgeBody": { + "message": "With identities, quickly autofill long registration or contact forms." + }, + "newNoteNudgeTitle": { + "message": "Keep your sensitive data safe" + }, + "newNoteNudgeBody": { + "message": "With notes, securely store sensitive data like banking or insurance details." + }, + "newSshNudgeTitle": { + "message": "Developer-friendly SSH access" + }, + "newSshNudgeBody": { + "message": "Store your keys and connect with the SSH agent for fast, encrypted authentication." } } diff --git a/apps/browser/src/_locales/gl/messages.json b/apps/browser/src/_locales/gl/messages.json index b2b843d7830..40823d54799 100644 --- a/apps/browser/src/_locales/gl/messages.json +++ b/apps/browser/src/_locales/gl/messages.json @@ -2,6 +2,9 @@ "appName": { "message": "Bitwarden" }, + "appLogoLabel": { + "message": "Bitwarden logo" + }, "extName": { "message": "Xestor de Contrasinais Bitwarden", "description": "Extension name, MUST be less than 40 characters (Safari restriction)" @@ -883,6 +886,9 @@ "followTheStepsBelowToFinishLoggingIn": { "message": "Follow the steps below to finish logging in." }, + "followTheStepsBelowToFinishLoggingInWithSecurityKey": { + "message": "Follow the steps below to finish logging in with your security key." + }, "restartRegistration": { "message": "Reiniciar rexistro" }, @@ -1056,23 +1062,49 @@ "notificationAddSave": { "message": "Gardar" }, - "loginSaveSuccessDetails": { - "message": "$USERNAME$ saved to Bitwarden.", + "notificationViewAria": { + "message": "View $ITEMNAME$, opens in new window", "placeholders": { - "username": { + "itemName": { "content": "$1" } }, - "description": "Shown to user after login is saved." + "description": "Aria label for the view button in notification bar confirmation message" }, - "loginUpdatedSuccessDetails": { - "message": "$USERNAME$ updated in Bitwarden.", + "notificationEditTooltip": { + "message": "Edit before saving", + "description": "Tooltip and Aria label for edit button on cipher item" + }, + "newNotification": { + "message": "New notification" + }, + "labelWithNotification": { + "message": "$LABEL$: New notification", + "description": "Label for the notification with a new login suggestion.", "placeholders": { - "username": { + "label": { + "content": "$1", + "example": "Login" + } + } + }, + "loginSaveConfirmation": { + "message": "$ITEMNAME$ saved to Bitwarden.", + "placeholders": { + "itemName": { "content": "$1" } }, - "description": "Shown to user after login is updated." + "description": "Shown to user after item is saved." + }, + "loginUpdatedConfirmation": { + "message": "$ITEMNAME$ updated in Bitwarden.", + "placeholders": { + "itemName": { + "content": "$1" + } + }, + "description": "Shown to user after item is updated." }, "saveAsNewLoginAction": { "message": "Save as new login", @@ -1082,12 +1114,12 @@ "message": "Update login", "description": "Button text for updating an existing login entry." }, - "saveLoginPrompt": { - "message": "Save login?", + "saveLogin": { + "message": "Save login", "description": "Prompt asking the user if they want to save their login details." }, - "updateLoginPrompt": { - "message": "Update existing login?", + "updateLogin": { + "message": "Update existing login", "description": "Prompt asking the user if they want to update an existing login entry." }, "loginSaveSuccess": { @@ -4928,8 +4960,8 @@ "message": "Contrasinal xerado de novo", "description": "Notification message for when a password has been regenerated" }, - "saveLoginToBitwarden": { - "message": "Gardar credenciais en Bitwarden?", + "saveToBitwarden": { + "message": "Save to Bitwarden", "description": "Confirmation message for saving a login to Bitwarden" }, "spaceCharacterDescriptor": { @@ -5169,6 +5201,12 @@ "changeAtRiskPassword": { "message": "Change at-risk password" }, + "settingsVaultOptions": { + "message": "Vault options" + }, + "emptyVaultDescription": { + "message": "The vault protects more than just your passwords. Store secure logins, IDs, cards and notes securely here." + }, "introCarouselLabel": { "message": "Welcome to Bitwarden" }, @@ -5195,5 +5233,50 @@ }, "secureDevicesBody": { "message": "Save unlimited passwords across unlimited devices with Bitwarden mobile, browser, and desktop apps." + }, + "emptyVaultNudgeTitle": { + "message": "Import existing passwords" + }, + "emptyVaultNudgeBody": { + "message": "Use the importer to quickly transfer logins to Bitwarden without manually adding them." + }, + "emptyVaultNudgeButton": { + "message": "Import now" + }, + "hasItemsVaultNudgeTitle": { + "message": "Welcome to your vault!" + }, + "hasItemsVaultNudgeBody": { + "message": "Autofill items for the current page\nFavorite items for easy access\nSearch your vault for something else" + }, + "newLoginNudgeTitle": { + "message": "Save time with autofill" + }, + "newLoginNudgeBody": { + "message": "Include a Website so this login appears as an autofill suggestion." + }, + "newCardNudgeTitle": { + "message": "Seamless online checkout" + }, + "newCardNudgeBody": { + "message": "With cards, easily autofill payment forms securely and accurately." + }, + "newIdentityNudgeTitle": { + "message": "Simplify creating accounts" + }, + "newIdentityNudgeBody": { + "message": "With identities, quickly autofill long registration or contact forms." + }, + "newNoteNudgeTitle": { + "message": "Keep your sensitive data safe" + }, + "newNoteNudgeBody": { + "message": "With notes, securely store sensitive data like banking or insurance details." + }, + "newSshNudgeTitle": { + "message": "Developer-friendly SSH access" + }, + "newSshNudgeBody": { + "message": "Store your keys and connect with the SSH agent for fast, encrypted authentication." } } diff --git a/apps/browser/src/_locales/he/messages.json b/apps/browser/src/_locales/he/messages.json index fd38df53906..a41386d9494 100644 --- a/apps/browser/src/_locales/he/messages.json +++ b/apps/browser/src/_locales/he/messages.json @@ -2,6 +2,9 @@ "appName": { "message": "Bitwarden" }, + "appLogoLabel": { + "message": "Bitwarden logo" + }, "extName": { "message": "מנהל הסיסמאות Bitwarden", "description": "Extension name, MUST be less than 40 characters (Safari restriction)" @@ -883,6 +886,9 @@ "followTheStepsBelowToFinishLoggingIn": { "message": "עקוב אחר השלבים למטה כדי לסיים להיכנס." }, + "followTheStepsBelowToFinishLoggingInWithSecurityKey": { + "message": "Follow the steps below to finish logging in with your security key." + }, "restartRegistration": { "message": "התחל הרשמה מחדש" }, @@ -1056,23 +1062,49 @@ "notificationAddSave": { "message": "שמור" }, - "loginSaveSuccessDetails": { - "message": "$USERNAME$ נשמר אל Bitwarden.", + "notificationViewAria": { + "message": "View $ITEMNAME$, opens in new window", "placeholders": { - "username": { + "itemName": { "content": "$1" } }, - "description": "Shown to user after login is saved." + "description": "Aria label for the view button in notification bar confirmation message" }, - "loginUpdatedSuccessDetails": { - "message": "$USERNAME$ עודכן ב־Bitwarden.", + "notificationEditTooltip": { + "message": "Edit before saving", + "description": "Tooltip and Aria label for edit button on cipher item" + }, + "newNotification": { + "message": "New notification" + }, + "labelWithNotification": { + "message": "$LABEL$: New notification", + "description": "Label for the notification with a new login suggestion.", "placeholders": { - "username": { + "label": { + "content": "$1", + "example": "Login" + } + } + }, + "loginSaveConfirmation": { + "message": "$ITEMNAME$ saved to Bitwarden.", + "placeholders": { + "itemName": { "content": "$1" } }, - "description": "Shown to user after login is updated." + "description": "Shown to user after item is saved." + }, + "loginUpdatedConfirmation": { + "message": "$ITEMNAME$ updated in Bitwarden.", + "placeholders": { + "itemName": { + "content": "$1" + } + }, + "description": "Shown to user after item is updated." }, "saveAsNewLoginAction": { "message": "שמור ככניסה חדשה", @@ -1082,12 +1114,12 @@ "message": "עדכן כניסה", "description": "Button text for updating an existing login entry." }, - "saveLoginPrompt": { - "message": "לשמור כניסה?", + "saveLogin": { + "message": "Save login", "description": "Prompt asking the user if they want to save their login details." }, - "updateLoginPrompt": { - "message": "לעדכן כניסה קיימת?", + "updateLogin": { + "message": "Update existing login", "description": "Prompt asking the user if they want to update an existing login entry." }, "loginSaveSuccess": { @@ -4928,8 +4960,8 @@ "message": "סיסמה נוצרה מחדש", "description": "Notification message for when a password has been regenerated" }, - "saveLoginToBitwarden": { - "message": "לשמור כניסה ב־Bitwarden?", + "saveToBitwarden": { + "message": "Save to Bitwarden", "description": "Confirmation message for saving a login to Bitwarden" }, "spaceCharacterDescriptor": { @@ -5169,6 +5201,12 @@ "changeAtRiskPassword": { "message": "שנה סיסמה בסיכון" }, + "settingsVaultOptions": { + "message": "Vault options" + }, + "emptyVaultDescription": { + "message": "The vault protects more than just your passwords. Store secure logins, IDs, cards and notes securely here." + }, "introCarouselLabel": { "message": "Welcome to Bitwarden" }, @@ -5195,5 +5233,50 @@ }, "secureDevicesBody": { "message": "Save unlimited passwords across unlimited devices with Bitwarden mobile, browser, and desktop apps." + }, + "emptyVaultNudgeTitle": { + "message": "Import existing passwords" + }, + "emptyVaultNudgeBody": { + "message": "Use the importer to quickly transfer logins to Bitwarden without manually adding them." + }, + "emptyVaultNudgeButton": { + "message": "Import now" + }, + "hasItemsVaultNudgeTitle": { + "message": "Welcome to your vault!" + }, + "hasItemsVaultNudgeBody": { + "message": "Autofill items for the current page\nFavorite items for easy access\nSearch your vault for something else" + }, + "newLoginNudgeTitle": { + "message": "Save time with autofill" + }, + "newLoginNudgeBody": { + "message": "Include a Website so this login appears as an autofill suggestion." + }, + "newCardNudgeTitle": { + "message": "Seamless online checkout" + }, + "newCardNudgeBody": { + "message": "With cards, easily autofill payment forms securely and accurately." + }, + "newIdentityNudgeTitle": { + "message": "Simplify creating accounts" + }, + "newIdentityNudgeBody": { + "message": "With identities, quickly autofill long registration or contact forms." + }, + "newNoteNudgeTitle": { + "message": "Keep your sensitive data safe" + }, + "newNoteNudgeBody": { + "message": "With notes, securely store sensitive data like banking or insurance details." + }, + "newSshNudgeTitle": { + "message": "Developer-friendly SSH access" + }, + "newSshNudgeBody": { + "message": "Store your keys and connect with the SSH agent for fast, encrypted authentication." } } diff --git a/apps/browser/src/_locales/hi/messages.json b/apps/browser/src/_locales/hi/messages.json index 813b69b9079..d4fff520b87 100644 --- a/apps/browser/src/_locales/hi/messages.json +++ b/apps/browser/src/_locales/hi/messages.json @@ -2,6 +2,9 @@ "appName": { "message": "bitwarden" }, + "appLogoLabel": { + "message": "Bitwarden logo" + }, "extName": { "message": "बिटवार्डन पासवर्ड मैनेजर", "description": "Extension name, MUST be less than 40 characters (Safari restriction)" @@ -883,6 +886,9 @@ "followTheStepsBelowToFinishLoggingIn": { "message": "Follow the steps below to finish logging in." }, + "followTheStepsBelowToFinishLoggingInWithSecurityKey": { + "message": "Follow the steps below to finish logging in with your security key." + }, "restartRegistration": { "message": "Restart registration" }, @@ -1056,23 +1062,49 @@ "notificationAddSave": { "message": "Yes, Save Now" }, - "loginSaveSuccessDetails": { - "message": "$USERNAME$ को बिटवार्डन में सहेजा गया।", + "notificationViewAria": { + "message": "View $ITEMNAME$, opens in new window", "placeholders": { - "username": { + "itemName": { "content": "$1" } }, - "description": "Shown to user after login is saved." + "description": "Aria label for the view button in notification bar confirmation message" }, - "loginUpdatedSuccessDetails": { - "message": "$USERNAME$ को बिटवार्डन में अपडेट किया गया।", + "notificationEditTooltip": { + "message": "Edit before saving", + "description": "Tooltip and Aria label for edit button on cipher item" + }, + "newNotification": { + "message": "New notification" + }, + "labelWithNotification": { + "message": "$LABEL$: New notification", + "description": "Label for the notification with a new login suggestion.", "placeholders": { - "username": { + "label": { + "content": "$1", + "example": "Login" + } + } + }, + "loginSaveConfirmation": { + "message": "$ITEMNAME$ saved to Bitwarden.", + "placeholders": { + "itemName": { "content": "$1" } }, - "description": "Shown to user after login is updated." + "description": "Shown to user after item is saved." + }, + "loginUpdatedConfirmation": { + "message": "$ITEMNAME$ updated in Bitwarden.", + "placeholders": { + "itemName": { + "content": "$1" + } + }, + "description": "Shown to user after item is updated." }, "saveAsNewLoginAction": { "message": "Save as new login", @@ -1082,12 +1114,12 @@ "message": "Update login", "description": "Button text for updating an existing login entry." }, - "saveLoginPrompt": { - "message": "Save login?", + "saveLogin": { + "message": "Save login", "description": "Prompt asking the user if they want to save their login details." }, - "updateLoginPrompt": { - "message": "Update existing login?", + "updateLogin": { + "message": "Update existing login", "description": "Prompt asking the user if they want to update an existing login entry." }, "loginSaveSuccess": { @@ -4928,8 +4960,8 @@ "message": "Password regenerated", "description": "Notification message for when a password has been regenerated" }, - "saveLoginToBitwarden": { - "message": "Save login to Bitwarden?", + "saveToBitwarden": { + "message": "Save to Bitwarden", "description": "Confirmation message for saving a login to Bitwarden" }, "spaceCharacterDescriptor": { @@ -5169,6 +5201,12 @@ "changeAtRiskPassword": { "message": "Change at-risk password" }, + "settingsVaultOptions": { + "message": "Vault options" + }, + "emptyVaultDescription": { + "message": "The vault protects more than just your passwords. Store secure logins, IDs, cards and notes securely here." + }, "introCarouselLabel": { "message": "Welcome to Bitwarden" }, @@ -5195,5 +5233,50 @@ }, "secureDevicesBody": { "message": "Save unlimited passwords across unlimited devices with Bitwarden mobile, browser, and desktop apps." + }, + "emptyVaultNudgeTitle": { + "message": "Import existing passwords" + }, + "emptyVaultNudgeBody": { + "message": "Use the importer to quickly transfer logins to Bitwarden without manually adding them." + }, + "emptyVaultNudgeButton": { + "message": "Import now" + }, + "hasItemsVaultNudgeTitle": { + "message": "Welcome to your vault!" + }, + "hasItemsVaultNudgeBody": { + "message": "Autofill items for the current page\nFavorite items for easy access\nSearch your vault for something else" + }, + "newLoginNudgeTitle": { + "message": "Save time with autofill" + }, + "newLoginNudgeBody": { + "message": "Include a Website so this login appears as an autofill suggestion." + }, + "newCardNudgeTitle": { + "message": "Seamless online checkout" + }, + "newCardNudgeBody": { + "message": "With cards, easily autofill payment forms securely and accurately." + }, + "newIdentityNudgeTitle": { + "message": "Simplify creating accounts" + }, + "newIdentityNudgeBody": { + "message": "With identities, quickly autofill long registration or contact forms." + }, + "newNoteNudgeTitle": { + "message": "Keep your sensitive data safe" + }, + "newNoteNudgeBody": { + "message": "With notes, securely store sensitive data like banking or insurance details." + }, + "newSshNudgeTitle": { + "message": "Developer-friendly SSH access" + }, + "newSshNudgeBody": { + "message": "Store your keys and connect with the SSH agent for fast, encrypted authentication." } } diff --git a/apps/browser/src/_locales/hr/messages.json b/apps/browser/src/_locales/hr/messages.json index 896593332a4..f3e4bd840c7 100644 --- a/apps/browser/src/_locales/hr/messages.json +++ b/apps/browser/src/_locales/hr/messages.json @@ -2,6 +2,9 @@ "appName": { "message": "Bitwarden" }, + "appLogoLabel": { + "message": "Bitwarden logo" + }, "extName": { "message": "Bitwarden upravitelj lozinki", "description": "Extension name, MUST be less than 40 characters (Safari restriction)" @@ -883,6 +886,9 @@ "followTheStepsBelowToFinishLoggingIn": { "message": "Prati korake za dovršetak prijave." }, + "followTheStepsBelowToFinishLoggingInWithSecurityKey": { + "message": "Follow the steps below to finish logging in with your security key." + }, "restartRegistration": { "message": "Ponovno pokreni registraciju" }, @@ -1056,23 +1062,49 @@ "notificationAddSave": { "message": "Spremi" }, - "loginSaveSuccessDetails": { - "message": "$USERNAME$ spremljeno u Bitwarden.", + "notificationViewAria": { + "message": "View $ITEMNAME$, opens in new window", "placeholders": { - "username": { + "itemName": { "content": "$1" } }, - "description": "Shown to user after login is saved." + "description": "Aria label for the view button in notification bar confirmation message" }, - "loginUpdatedSuccessDetails": { - "message": "$USERNAME$ ažurirano u Bitwardenu.", + "notificationEditTooltip": { + "message": "Edit before saving", + "description": "Tooltip and Aria label for edit button on cipher item" + }, + "newNotification": { + "message": "New notification" + }, + "labelWithNotification": { + "message": "$LABEL$: New notification", + "description": "Label for the notification with a new login suggestion.", "placeholders": { - "username": { + "label": { + "content": "$1", + "example": "Login" + } + } + }, + "loginSaveConfirmation": { + "message": "$ITEMNAME$ saved to Bitwarden.", + "placeholders": { + "itemName": { "content": "$1" } }, - "description": "Shown to user after login is updated." + "description": "Shown to user after item is saved." + }, + "loginUpdatedConfirmation": { + "message": "$ITEMNAME$ updated in Bitwarden.", + "placeholders": { + "itemName": { + "content": "$1" + } + }, + "description": "Shown to user after item is updated." }, "saveAsNewLoginAction": { "message": "Spremi novu prijavu", @@ -1082,12 +1114,12 @@ "message": "Ažuriraj prijavu", "description": "Button text for updating an existing login entry." }, - "saveLoginPrompt": { - "message": "Spremiti prijavu?", + "saveLogin": { + "message": "Save login", "description": "Prompt asking the user if they want to save their login details." }, - "updateLoginPrompt": { - "message": "Ažurirati postojeću prijavu?", + "updateLogin": { + "message": "Update existing login", "description": "Prompt asking the user if they want to update an existing login entry." }, "loginSaveSuccess": { @@ -4928,8 +4960,8 @@ "message": "Lozinka re-generirana", "description": "Notification message for when a password has been regenerated" }, - "saveLoginToBitwarden": { - "message": "Spremi prijavu u Bitwarden?", + "saveToBitwarden": { + "message": "Save to Bitwarden", "description": "Confirmation message for saving a login to Bitwarden" }, "spaceCharacterDescriptor": { @@ -5169,6 +5201,12 @@ "changeAtRiskPassword": { "message": "Promijeni rizičnu lozinku" }, + "settingsVaultOptions": { + "message": "Vault options" + }, + "emptyVaultDescription": { + "message": "The vault protects more than just your passwords. Store secure logins, IDs, cards and notes securely here." + }, "introCarouselLabel": { "message": "Welcome to Bitwarden" }, @@ -5195,5 +5233,50 @@ }, "secureDevicesBody": { "message": "Save unlimited passwords across unlimited devices with Bitwarden mobile, browser, and desktop apps." + }, + "emptyVaultNudgeTitle": { + "message": "Import existing passwords" + }, + "emptyVaultNudgeBody": { + "message": "Use the importer to quickly transfer logins to Bitwarden without manually adding them." + }, + "emptyVaultNudgeButton": { + "message": "Import now" + }, + "hasItemsVaultNudgeTitle": { + "message": "Welcome to your vault!" + }, + "hasItemsVaultNudgeBody": { + "message": "Autofill items for the current page\nFavorite items for easy access\nSearch your vault for something else" + }, + "newLoginNudgeTitle": { + "message": "Save time with autofill" + }, + "newLoginNudgeBody": { + "message": "Include a Website so this login appears as an autofill suggestion." + }, + "newCardNudgeTitle": { + "message": "Seamless online checkout" + }, + "newCardNudgeBody": { + "message": "With cards, easily autofill payment forms securely and accurately." + }, + "newIdentityNudgeTitle": { + "message": "Simplify creating accounts" + }, + "newIdentityNudgeBody": { + "message": "With identities, quickly autofill long registration or contact forms." + }, + "newNoteNudgeTitle": { + "message": "Keep your sensitive data safe" + }, + "newNoteNudgeBody": { + "message": "With notes, securely store sensitive data like banking or insurance details." + }, + "newSshNudgeTitle": { + "message": "Developer-friendly SSH access" + }, + "newSshNudgeBody": { + "message": "Store your keys and connect with the SSH agent for fast, encrypted authentication." } } diff --git a/apps/browser/src/_locales/hu/messages.json b/apps/browser/src/_locales/hu/messages.json index f58e542c77f..07161af5727 100644 --- a/apps/browser/src/_locales/hu/messages.json +++ b/apps/browser/src/_locales/hu/messages.json @@ -2,6 +2,9 @@ "appName": { "message": "Bitwarden" }, + "appLogoLabel": { + "message": "Bitwarden logó" + }, "extName": { "message": "Bitwarden Password Manager", "description": "Extension name, MUST be less than 40 characters (Safari restriction)" @@ -883,6 +886,9 @@ "followTheStepsBelowToFinishLoggingIn": { "message": "Kövessük az alábbi lépéseket a bejelentkezés befejezéséhez." }, + "followTheStepsBelowToFinishLoggingInWithSecurityKey": { + "message": "Kövessük az alábbi lépéseket a biztonsági kulccsal bejelentkezés befejezéséhez." + }, "restartRegistration": { "message": "Restart registration" }, @@ -1056,23 +1062,49 @@ "notificationAddSave": { "message": "Mentés" }, - "loginSaveSuccessDetails": { - "message": "$USERNAME$ mentésre került a Bitwardenben.", + "notificationViewAria": { + "message": "$ITEMNAME$ megtekintése, megnyitás új ablakban", "placeholders": { - "username": { + "itemName": { "content": "$1" } }, - "description": "Shown to user after login is saved." + "description": "Aria label for the view button in notification bar confirmation message" }, - "loginUpdatedSuccessDetails": { - "message": "$USERNAME$ frissítésre került a Bitwardenben.", + "notificationEditTooltip": { + "message": "Szerkesztés mentés előtt", + "description": "Tooltip and Aria label for edit button on cipher item" + }, + "newNotification": { + "message": "Új értesítés" + }, + "labelWithNotification": { + "message": "$LABEL$: Új értesítés", + "description": "Label for the notification with a new login suggestion.", "placeholders": { - "username": { + "label": { + "content": "$1", + "example": "Login" + } + } + }, + "loginSaveConfirmation": { + "message": "$ITEMNAME$ mentésre került a Bitwardenben.", + "placeholders": { + "itemName": { "content": "$1" } }, - "description": "Shown to user after login is updated." + "description": "Shown to user after item is saved." + }, + "loginUpdatedConfirmation": { + "message": "$ITEMNAME$ frissítésre került a Bitwardenben.", + "placeholders": { + "itemName": { + "content": "$1" + } + }, + "description": "Shown to user after item is updated." }, "saveAsNewLoginAction": { "message": "Mentés új bejelentkezésként", @@ -1082,12 +1114,12 @@ "message": "Bejelentkezés frissítése", "description": "Button text for updating an existing login entry." }, - "saveLoginPrompt": { - "message": "Bejelentkezés mentése?", + "saveLogin": { + "message": "Bejelentkezés mentése", "description": "Prompt asking the user if they want to save their login details." }, - "updateLoginPrompt": { - "message": "Létező bejelentkezés frissítése?", + "updateLogin": { + "message": "Létező bejelentkezés frissítése", "description": "Prompt asking the user if they want to update an existing login entry." }, "loginSaveSuccess": { @@ -4928,8 +4960,8 @@ "message": "A jelszó generálásra került.", "description": "Notification message for when a password has been regenerated" }, - "saveLoginToBitwarden": { - "message": "Bejelentkezés mentése a Bitwardenbe?", + "saveToBitwarden": { + "message": "Mentés a Bitwardenbe", "description": "Confirmation message for saving a login to Bitwarden" }, "spaceCharacterDescriptor": { @@ -5169,6 +5201,12 @@ "changeAtRiskPassword": { "message": "Kockázatos jelszó megváltoztatása" }, + "settingsVaultOptions": { + "message": "Széf opciók" + }, + "emptyVaultDescription": { + "message": "A széf többre alkalmas, mint a jelszavak mentése. Menthetünk belépéseket, azonosítókat, kártyákat és feljegyzéseket teljes biztonságban." + }, "introCarouselLabel": { "message": "Üdvözlet a Bitwardenben" }, @@ -5195,5 +5233,50 @@ }, "secureDevicesBody": { "message": "Mentsünk el a korlátlan jelszót korlátlan számú eszközön a Bitwarden mobil, böngésző és asztali alkalmazásokkal." + }, + "emptyVaultNudgeTitle": { + "message": "Létező jelszavak importálása" + }, + "emptyVaultNudgeBody": { + "message": "Az importálóval gyorsan átvihetünk bejelentkezéseket a Bitwardenbe anélkül, hogy manuálisan hozzáadnánk azokat." + }, + "emptyVaultNudgeButton": { + "message": "Importálás most" + }, + "hasItemsVaultNudgeTitle": { + "message": "Üdvözlet a széfben!" + }, + "hasItemsVaultNudgeBody": { + "message": "Az aktuális oldal elemeinek automatikus kitöltése\nKedvenc elemek a könnyű hozzáférés érdekében\nValami más keresése a széfben" + }, + "newLoginNudgeTitle": { + "message": "Idő megtakarítás automatikus kitöltéssel" + }, + "newLoginNudgeBody": { + "message": "Adjunk meg egy webhelyet, hogy ez a bejelentkezési név automatikusan kitöltendő javaslatként jelenjen meg." + }, + "newCardNudgeTitle": { + "message": "Zökkenőmentes online fizetés" + }, + "newCardNudgeBody": { + "message": "Kártyákkal könnyedén, biztonságosan és pontosan tölthetjük ki automatikusan a fizetési űrlapokat." + }, + "newIdentityNudgeTitle": { + "message": "Egyszerűsítsük a fiókok létrehozását" + }, + "newIdentityNudgeBody": { + "message": "Azonosítókkal gyorsan automatikusan kitölthetjük a hosszú regisztrációs vagy kapcsolatfelvételi űrlapokat." + }, + "newNoteNudgeTitle": { + "message": "Tartsuk biztonságban az érzékeny adatokat" + }, + "newNoteNudgeBody": { + "message": "Jegyzetekkel biztonságosan tárolhatjuk az érzékeny adatokat, például a banki vagy biztosítási adatokat." + }, + "newSshNudgeTitle": { + "message": "Fejlesztőbarát SSH hozzáférés" + }, + "newSshNudgeBody": { + "message": "Tároljuk a kulcsokat és csatlakozzunk az SSH-ügynökhöz a gyors, titkosított hitelesítéshez." } } diff --git a/apps/browser/src/_locales/id/messages.json b/apps/browser/src/_locales/id/messages.json index 101fcf43795..5c77c54ecf4 100644 --- a/apps/browser/src/_locales/id/messages.json +++ b/apps/browser/src/_locales/id/messages.json @@ -2,6 +2,9 @@ "appName": { "message": "Bitwarden" }, + "appLogoLabel": { + "message": "Bitwarden logo" + }, "extName": { "message": "Pengelola Sandi Bitwarden", "description": "Extension name, MUST be less than 40 characters (Safari restriction)" @@ -883,6 +886,9 @@ "followTheStepsBelowToFinishLoggingIn": { "message": "Ikuti langkah-langkah di bawah untuk menyelesaikan log masuk." }, + "followTheStepsBelowToFinishLoggingInWithSecurityKey": { + "message": "Follow the steps below to finish logging in with your security key." + }, "restartRegistration": { "message": "Mulai ulang pendaftaran" }, @@ -1056,23 +1062,49 @@ "notificationAddSave": { "message": "Iya, Simpan Sekarang" }, - "loginSaveSuccessDetails": { - "message": "$USERNAME$ disimpan ke Bitwarden.", + "notificationViewAria": { + "message": "View $ITEMNAME$, opens in new window", "placeholders": { - "username": { + "itemName": { "content": "$1" } }, - "description": "Shown to user after login is saved." + "description": "Aria label for the view button in notification bar confirmation message" }, - "loginUpdatedSuccessDetails": { - "message": "$USERNAME$ diperbarui di Bitwarden.", + "notificationEditTooltip": { + "message": "Edit before saving", + "description": "Tooltip and Aria label for edit button on cipher item" + }, + "newNotification": { + "message": "New notification" + }, + "labelWithNotification": { + "message": "$LABEL$: New notification", + "description": "Label for the notification with a new login suggestion.", "placeholders": { - "username": { + "label": { + "content": "$1", + "example": "Login" + } + } + }, + "loginSaveConfirmation": { + "message": "$ITEMNAME$ saved to Bitwarden.", + "placeholders": { + "itemName": { "content": "$1" } }, - "description": "Shown to user after login is updated." + "description": "Shown to user after item is saved." + }, + "loginUpdatedConfirmation": { + "message": "$ITEMNAME$ updated in Bitwarden.", + "placeholders": { + "itemName": { + "content": "$1" + } + }, + "description": "Shown to user after item is updated." }, "saveAsNewLoginAction": { "message": "Simpan sebagai log masuk baru", @@ -1082,12 +1114,12 @@ "message": "Perbarui log masuk", "description": "Button text for updating an existing login entry." }, - "saveLoginPrompt": { - "message": "Simpan log masuk?", + "saveLogin": { + "message": "Save login", "description": "Prompt asking the user if they want to save their login details." }, - "updateLoginPrompt": { - "message": "Perbarui log masuk yang ada?", + "updateLogin": { + "message": "Update existing login", "description": "Prompt asking the user if they want to update an existing login entry." }, "loginSaveSuccess": { @@ -4928,8 +4960,8 @@ "message": "Kata sandi dibuat ulang", "description": "Notification message for when a password has been regenerated" }, - "saveLoginToBitwarden": { - "message": "Simpan log masuk ke Bitwarden?", + "saveToBitwarden": { + "message": "Save to Bitwarden", "description": "Confirmation message for saving a login to Bitwarden" }, "spaceCharacterDescriptor": { @@ -5169,6 +5201,12 @@ "changeAtRiskPassword": { "message": "Ubah kata sandi yang berrisiko" }, + "settingsVaultOptions": { + "message": "Vault options" + }, + "emptyVaultDescription": { + "message": "The vault protects more than just your passwords. Store secure logins, IDs, cards and notes securely here." + }, "introCarouselLabel": { "message": "Welcome to Bitwarden" }, @@ -5195,5 +5233,50 @@ }, "secureDevicesBody": { "message": "Save unlimited passwords across unlimited devices with Bitwarden mobile, browser, and desktop apps." + }, + "emptyVaultNudgeTitle": { + "message": "Import existing passwords" + }, + "emptyVaultNudgeBody": { + "message": "Use the importer to quickly transfer logins to Bitwarden without manually adding them." + }, + "emptyVaultNudgeButton": { + "message": "Import now" + }, + "hasItemsVaultNudgeTitle": { + "message": "Welcome to your vault!" + }, + "hasItemsVaultNudgeBody": { + "message": "Autofill items for the current page\nFavorite items for easy access\nSearch your vault for something else" + }, + "newLoginNudgeTitle": { + "message": "Save time with autofill" + }, + "newLoginNudgeBody": { + "message": "Include a Website so this login appears as an autofill suggestion." + }, + "newCardNudgeTitle": { + "message": "Seamless online checkout" + }, + "newCardNudgeBody": { + "message": "With cards, easily autofill payment forms securely and accurately." + }, + "newIdentityNudgeTitle": { + "message": "Simplify creating accounts" + }, + "newIdentityNudgeBody": { + "message": "With identities, quickly autofill long registration or contact forms." + }, + "newNoteNudgeTitle": { + "message": "Keep your sensitive data safe" + }, + "newNoteNudgeBody": { + "message": "With notes, securely store sensitive data like banking or insurance details." + }, + "newSshNudgeTitle": { + "message": "Developer-friendly SSH access" + }, + "newSshNudgeBody": { + "message": "Store your keys and connect with the SSH agent for fast, encrypted authentication." } } diff --git a/apps/browser/src/_locales/it/messages.json b/apps/browser/src/_locales/it/messages.json index 5f9a6ca7dca..724416cdcde 100644 --- a/apps/browser/src/_locales/it/messages.json +++ b/apps/browser/src/_locales/it/messages.json @@ -2,6 +2,9 @@ "appName": { "message": "Bitwarden" }, + "appLogoLabel": { + "message": "Bitwarden logo" + }, "extName": { "message": "Bitwarden Password Manager", "description": "Extension name, MUST be less than 40 characters (Safari restriction)" @@ -883,6 +886,9 @@ "followTheStepsBelowToFinishLoggingIn": { "message": "Segui i passaggi qui sotto per completare l'accesso." }, + "followTheStepsBelowToFinishLoggingInWithSecurityKey": { + "message": "Follow the steps below to finish logging in with your security key." + }, "restartRegistration": { "message": "Ricomincia la registrazione" }, @@ -1056,23 +1062,49 @@ "notificationAddSave": { "message": "Salva" }, - "loginSaveSuccessDetails": { - "message": "$USERNAME$ salvato in Bitwarden.", + "notificationViewAria": { + "message": "View $ITEMNAME$, opens in new window", "placeholders": { - "username": { + "itemName": { "content": "$1" } }, - "description": "Shown to user after login is saved." + "description": "Aria label for the view button in notification bar confirmation message" }, - "loginUpdatedSuccessDetails": { - "message": "$USERNAME$ aggiornato in Bitwarden.", + "notificationEditTooltip": { + "message": "Edit before saving", + "description": "Tooltip and Aria label for edit button on cipher item" + }, + "newNotification": { + "message": "New notification" + }, + "labelWithNotification": { + "message": "$LABEL$: New notification", + "description": "Label for the notification with a new login suggestion.", "placeholders": { - "username": { + "label": { + "content": "$1", + "example": "Login" + } + } + }, + "loginSaveConfirmation": { + "message": "$ITEMNAME$ saved to Bitwarden.", + "placeholders": { + "itemName": { "content": "$1" } }, - "description": "Shown to user after login is updated." + "description": "Shown to user after item is saved." + }, + "loginUpdatedConfirmation": { + "message": "$ITEMNAME$ updated in Bitwarden.", + "placeholders": { + "itemName": { + "content": "$1" + } + }, + "description": "Shown to user after item is updated." }, "saveAsNewLoginAction": { "message": "Salva come nuovo accesso", @@ -1082,12 +1114,12 @@ "message": "Aggiorna accesso", "description": "Button text for updating an existing login entry." }, - "saveLoginPrompt": { - "message": "Salvare l'accesso?", + "saveLogin": { + "message": "Save login", "description": "Prompt asking the user if they want to save their login details." }, - "updateLoginPrompt": { - "message": "Aggiornare l'accesso esistente?", + "updateLogin": { + "message": "Update existing login", "description": "Prompt asking the user if they want to update an existing login entry." }, "loginSaveSuccess": { @@ -4928,8 +4960,8 @@ "message": "Password rigenerata", "description": "Notification message for when a password has been regenerated" }, - "saveLoginToBitwarden": { - "message": "Salvare il login su Bitwarden?", + "saveToBitwarden": { + "message": "Save to Bitwarden", "description": "Confirmation message for saving a login to Bitwarden" }, "spaceCharacterDescriptor": { @@ -5169,6 +5201,12 @@ "changeAtRiskPassword": { "message": "Cambia parola d'accesso a rischio" }, + "settingsVaultOptions": { + "message": "Vault options" + }, + "emptyVaultDescription": { + "message": "The vault protects more than just your passwords. Store secure logins, IDs, cards and notes securely here." + }, "introCarouselLabel": { "message": "Welcome to Bitwarden" }, @@ -5195,5 +5233,50 @@ }, "secureDevicesBody": { "message": "Save unlimited passwords across unlimited devices with Bitwarden mobile, browser, and desktop apps." + }, + "emptyVaultNudgeTitle": { + "message": "Import existing passwords" + }, + "emptyVaultNudgeBody": { + "message": "Use the importer to quickly transfer logins to Bitwarden without manually adding them." + }, + "emptyVaultNudgeButton": { + "message": "Import now" + }, + "hasItemsVaultNudgeTitle": { + "message": "Welcome to your vault!" + }, + "hasItemsVaultNudgeBody": { + "message": "Autofill items for the current page\nFavorite items for easy access\nSearch your vault for something else" + }, + "newLoginNudgeTitle": { + "message": "Save time with autofill" + }, + "newLoginNudgeBody": { + "message": "Include a Website so this login appears as an autofill suggestion." + }, + "newCardNudgeTitle": { + "message": "Seamless online checkout" + }, + "newCardNudgeBody": { + "message": "With cards, easily autofill payment forms securely and accurately." + }, + "newIdentityNudgeTitle": { + "message": "Simplify creating accounts" + }, + "newIdentityNudgeBody": { + "message": "With identities, quickly autofill long registration or contact forms." + }, + "newNoteNudgeTitle": { + "message": "Keep your sensitive data safe" + }, + "newNoteNudgeBody": { + "message": "With notes, securely store sensitive data like banking or insurance details." + }, + "newSshNudgeTitle": { + "message": "Developer-friendly SSH access" + }, + "newSshNudgeBody": { + "message": "Store your keys and connect with the SSH agent for fast, encrypted authentication." } } diff --git a/apps/browser/src/_locales/ja/messages.json b/apps/browser/src/_locales/ja/messages.json index 34b15699437..be70cd7d939 100644 --- a/apps/browser/src/_locales/ja/messages.json +++ b/apps/browser/src/_locales/ja/messages.json @@ -2,6 +2,9 @@ "appName": { "message": "Bitwarden" }, + "appLogoLabel": { + "message": "Bitwarden logo" + }, "extName": { "message": "Bitwarden パスワードマネージャー", "description": "Extension name, MUST be less than 40 characters (Safari restriction)" @@ -883,6 +886,9 @@ "followTheStepsBelowToFinishLoggingIn": { "message": "以下の手順に従ってログインを完了してください。" }, + "followTheStepsBelowToFinishLoggingInWithSecurityKey": { + "message": "Follow the steps below to finish logging in with your security key." + }, "restartRegistration": { "message": "登録を再度始める" }, @@ -1056,23 +1062,49 @@ "notificationAddSave": { "message": "保存する" }, - "loginSaveSuccessDetails": { - "message": "$USERNAME$ を Bitwarden に保存しました。", + "notificationViewAria": { + "message": "View $ITEMNAME$, opens in new window", "placeholders": { - "username": { + "itemName": { "content": "$1" } }, - "description": "Shown to user after login is saved." + "description": "Aria label for the view button in notification bar confirmation message" }, - "loginUpdatedSuccessDetails": { - "message": "$USERNAME$ を Bitwarden 内で更新しました。", + "notificationEditTooltip": { + "message": "Edit before saving", + "description": "Tooltip and Aria label for edit button on cipher item" + }, + "newNotification": { + "message": "New notification" + }, + "labelWithNotification": { + "message": "$LABEL$: New notification", + "description": "Label for the notification with a new login suggestion.", "placeholders": { - "username": { + "label": { + "content": "$1", + "example": "Login" + } + } + }, + "loginSaveConfirmation": { + "message": "$ITEMNAME$ saved to Bitwarden.", + "placeholders": { + "itemName": { "content": "$1" } }, - "description": "Shown to user after login is updated." + "description": "Shown to user after item is saved." + }, + "loginUpdatedConfirmation": { + "message": "$ITEMNAME$ updated in Bitwarden.", + "placeholders": { + "itemName": { + "content": "$1" + } + }, + "description": "Shown to user after item is updated." }, "saveAsNewLoginAction": { "message": "新規のログイン情報として保存", @@ -1082,12 +1114,12 @@ "message": "ログイン情報を更新", "description": "Button text for updating an existing login entry." }, - "saveLoginPrompt": { - "message": "ログイン情報を保存しますか?", + "saveLogin": { + "message": "Save login", "description": "Prompt asking the user if they want to save their login details." }, - "updateLoginPrompt": { - "message": "既存のログイン情報を更新しますか?", + "updateLogin": { + "message": "Update existing login", "description": "Prompt asking the user if they want to update an existing login entry." }, "loginSaveSuccess": { @@ -4928,8 +4960,8 @@ "message": "パスワードを再生成しました", "description": "Notification message for when a password has been regenerated" }, - "saveLoginToBitwarden": { - "message": "Bitwarden にログイン情報を保存しますか?", + "saveToBitwarden": { + "message": "Save to Bitwarden", "description": "Confirmation message for saving a login to Bitwarden" }, "spaceCharacterDescriptor": { @@ -5169,6 +5201,12 @@ "changeAtRiskPassword": { "message": "危険なパスワードの変更" }, + "settingsVaultOptions": { + "message": "Vault options" + }, + "emptyVaultDescription": { + "message": "The vault protects more than just your passwords. Store secure logins, IDs, cards and notes securely here." + }, "introCarouselLabel": { "message": "Bitwarden へようこそ" }, @@ -5195,5 +5233,50 @@ }, "secureDevicesBody": { "message": "Bitwarden のモバイル、ブラウザ、デスクトップアプリでは、保存できるパスワード数やデバイス数に制限はありません。" + }, + "emptyVaultNudgeTitle": { + "message": "Import existing passwords" + }, + "emptyVaultNudgeBody": { + "message": "Use the importer to quickly transfer logins to Bitwarden without manually adding them." + }, + "emptyVaultNudgeButton": { + "message": "Import now" + }, + "hasItemsVaultNudgeTitle": { + "message": "Welcome to your vault!" + }, + "hasItemsVaultNudgeBody": { + "message": "Autofill items for the current page\nFavorite items for easy access\nSearch your vault for something else" + }, + "newLoginNudgeTitle": { + "message": "Save time with autofill" + }, + "newLoginNudgeBody": { + "message": "Include a Website so this login appears as an autofill suggestion." + }, + "newCardNudgeTitle": { + "message": "Seamless online checkout" + }, + "newCardNudgeBody": { + "message": "With cards, easily autofill payment forms securely and accurately." + }, + "newIdentityNudgeTitle": { + "message": "Simplify creating accounts" + }, + "newIdentityNudgeBody": { + "message": "With identities, quickly autofill long registration or contact forms." + }, + "newNoteNudgeTitle": { + "message": "Keep your sensitive data safe" + }, + "newNoteNudgeBody": { + "message": "With notes, securely store sensitive data like banking or insurance details." + }, + "newSshNudgeTitle": { + "message": "Developer-friendly SSH access" + }, + "newSshNudgeBody": { + "message": "Store your keys and connect with the SSH agent for fast, encrypted authentication." } } diff --git a/apps/browser/src/_locales/ka/messages.json b/apps/browser/src/_locales/ka/messages.json index f261f69904d..bb4a9be2a6c 100644 --- a/apps/browser/src/_locales/ka/messages.json +++ b/apps/browser/src/_locales/ka/messages.json @@ -2,6 +2,9 @@ "appName": { "message": "Bitwarden" }, + "appLogoLabel": { + "message": "Bitwarden logo" + }, "extName": { "message": "Bitwarden Password Manager", "description": "Extension name, MUST be less than 40 characters (Safari restriction)" @@ -883,6 +886,9 @@ "followTheStepsBelowToFinishLoggingIn": { "message": "Follow the steps below to finish logging in." }, + "followTheStepsBelowToFinishLoggingInWithSecurityKey": { + "message": "Follow the steps below to finish logging in with your security key." + }, "restartRegistration": { "message": "Restart registration" }, @@ -1056,23 +1062,49 @@ "notificationAddSave": { "message": "შენახვა" }, - "loginSaveSuccessDetails": { - "message": "$USERNAME$ saved to Bitwarden.", + "notificationViewAria": { + "message": "View $ITEMNAME$, opens in new window", "placeholders": { - "username": { + "itemName": { "content": "$1" } }, - "description": "Shown to user after login is saved." + "description": "Aria label for the view button in notification bar confirmation message" }, - "loginUpdatedSuccessDetails": { - "message": "$USERNAME$ updated in Bitwarden.", + "notificationEditTooltip": { + "message": "Edit before saving", + "description": "Tooltip and Aria label for edit button on cipher item" + }, + "newNotification": { + "message": "New notification" + }, + "labelWithNotification": { + "message": "$LABEL$: New notification", + "description": "Label for the notification with a new login suggestion.", "placeholders": { - "username": { + "label": { + "content": "$1", + "example": "Login" + } + } + }, + "loginSaveConfirmation": { + "message": "$ITEMNAME$ saved to Bitwarden.", + "placeholders": { + "itemName": { "content": "$1" } }, - "description": "Shown to user after login is updated." + "description": "Shown to user after item is saved." + }, + "loginUpdatedConfirmation": { + "message": "$ITEMNAME$ updated in Bitwarden.", + "placeholders": { + "itemName": { + "content": "$1" + } + }, + "description": "Shown to user after item is updated." }, "saveAsNewLoginAction": { "message": "Save as new login", @@ -1082,12 +1114,12 @@ "message": "Update login", "description": "Button text for updating an existing login entry." }, - "saveLoginPrompt": { - "message": "Save login?", + "saveLogin": { + "message": "Save login", "description": "Prompt asking the user if they want to save their login details." }, - "updateLoginPrompt": { - "message": "Update existing login?", + "updateLogin": { + "message": "Update existing login", "description": "Prompt asking the user if they want to update an existing login entry." }, "loginSaveSuccess": { @@ -4928,8 +4960,8 @@ "message": "Password regenerated", "description": "Notification message for when a password has been regenerated" }, - "saveLoginToBitwarden": { - "message": "Save login to Bitwarden?", + "saveToBitwarden": { + "message": "Save to Bitwarden", "description": "Confirmation message for saving a login to Bitwarden" }, "spaceCharacterDescriptor": { @@ -5169,6 +5201,12 @@ "changeAtRiskPassword": { "message": "Change at-risk password" }, + "settingsVaultOptions": { + "message": "Vault options" + }, + "emptyVaultDescription": { + "message": "The vault protects more than just your passwords. Store secure logins, IDs, cards and notes securely here." + }, "introCarouselLabel": { "message": "Welcome to Bitwarden" }, @@ -5195,5 +5233,50 @@ }, "secureDevicesBody": { "message": "Save unlimited passwords across unlimited devices with Bitwarden mobile, browser, and desktop apps." + }, + "emptyVaultNudgeTitle": { + "message": "Import existing passwords" + }, + "emptyVaultNudgeBody": { + "message": "Use the importer to quickly transfer logins to Bitwarden without manually adding them." + }, + "emptyVaultNudgeButton": { + "message": "Import now" + }, + "hasItemsVaultNudgeTitle": { + "message": "Welcome to your vault!" + }, + "hasItemsVaultNudgeBody": { + "message": "Autofill items for the current page\nFavorite items for easy access\nSearch your vault for something else" + }, + "newLoginNudgeTitle": { + "message": "Save time with autofill" + }, + "newLoginNudgeBody": { + "message": "Include a Website so this login appears as an autofill suggestion." + }, + "newCardNudgeTitle": { + "message": "Seamless online checkout" + }, + "newCardNudgeBody": { + "message": "With cards, easily autofill payment forms securely and accurately." + }, + "newIdentityNudgeTitle": { + "message": "Simplify creating accounts" + }, + "newIdentityNudgeBody": { + "message": "With identities, quickly autofill long registration or contact forms." + }, + "newNoteNudgeTitle": { + "message": "Keep your sensitive data safe" + }, + "newNoteNudgeBody": { + "message": "With notes, securely store sensitive data like banking or insurance details." + }, + "newSshNudgeTitle": { + "message": "Developer-friendly SSH access" + }, + "newSshNudgeBody": { + "message": "Store your keys and connect with the SSH agent for fast, encrypted authentication." } } diff --git a/apps/browser/src/_locales/km/messages.json b/apps/browser/src/_locales/km/messages.json index 7bec167e1c6..99a09c75d41 100644 --- a/apps/browser/src/_locales/km/messages.json +++ b/apps/browser/src/_locales/km/messages.json @@ -2,6 +2,9 @@ "appName": { "message": "Bitwarden" }, + "appLogoLabel": { + "message": "Bitwarden logo" + }, "extName": { "message": "Bitwarden Password Manager", "description": "Extension name, MUST be less than 40 characters (Safari restriction)" @@ -883,6 +886,9 @@ "followTheStepsBelowToFinishLoggingIn": { "message": "Follow the steps below to finish logging in." }, + "followTheStepsBelowToFinishLoggingInWithSecurityKey": { + "message": "Follow the steps below to finish logging in with your security key." + }, "restartRegistration": { "message": "Restart registration" }, @@ -1056,23 +1062,49 @@ "notificationAddSave": { "message": "Save" }, - "loginSaveSuccessDetails": { - "message": "$USERNAME$ saved to Bitwarden.", + "notificationViewAria": { + "message": "View $ITEMNAME$, opens in new window", "placeholders": { - "username": { + "itemName": { "content": "$1" } }, - "description": "Shown to user after login is saved." + "description": "Aria label for the view button in notification bar confirmation message" }, - "loginUpdatedSuccessDetails": { - "message": "$USERNAME$ updated in Bitwarden.", + "notificationEditTooltip": { + "message": "Edit before saving", + "description": "Tooltip and Aria label for edit button on cipher item" + }, + "newNotification": { + "message": "New notification" + }, + "labelWithNotification": { + "message": "$LABEL$: New notification", + "description": "Label for the notification with a new login suggestion.", "placeholders": { - "username": { + "label": { + "content": "$1", + "example": "Login" + } + } + }, + "loginSaveConfirmation": { + "message": "$ITEMNAME$ saved to Bitwarden.", + "placeholders": { + "itemName": { "content": "$1" } }, - "description": "Shown to user after login is updated." + "description": "Shown to user after item is saved." + }, + "loginUpdatedConfirmation": { + "message": "$ITEMNAME$ updated in Bitwarden.", + "placeholders": { + "itemName": { + "content": "$1" + } + }, + "description": "Shown to user after item is updated." }, "saveAsNewLoginAction": { "message": "Save as new login", @@ -1082,12 +1114,12 @@ "message": "Update login", "description": "Button text for updating an existing login entry." }, - "saveLoginPrompt": { - "message": "Save login?", + "saveLogin": { + "message": "Save login", "description": "Prompt asking the user if they want to save their login details." }, - "updateLoginPrompt": { - "message": "Update existing login?", + "updateLogin": { + "message": "Update existing login", "description": "Prompt asking the user if they want to update an existing login entry." }, "loginSaveSuccess": { @@ -4928,8 +4960,8 @@ "message": "Password regenerated", "description": "Notification message for when a password has been regenerated" }, - "saveLoginToBitwarden": { - "message": "Save login to Bitwarden?", + "saveToBitwarden": { + "message": "Save to Bitwarden", "description": "Confirmation message for saving a login to Bitwarden" }, "spaceCharacterDescriptor": { @@ -5169,6 +5201,12 @@ "changeAtRiskPassword": { "message": "Change at-risk password" }, + "settingsVaultOptions": { + "message": "Vault options" + }, + "emptyVaultDescription": { + "message": "The vault protects more than just your passwords. Store secure logins, IDs, cards and notes securely here." + }, "introCarouselLabel": { "message": "Welcome to Bitwarden" }, @@ -5195,5 +5233,50 @@ }, "secureDevicesBody": { "message": "Save unlimited passwords across unlimited devices with Bitwarden mobile, browser, and desktop apps." + }, + "emptyVaultNudgeTitle": { + "message": "Import existing passwords" + }, + "emptyVaultNudgeBody": { + "message": "Use the importer to quickly transfer logins to Bitwarden without manually adding them." + }, + "emptyVaultNudgeButton": { + "message": "Import now" + }, + "hasItemsVaultNudgeTitle": { + "message": "Welcome to your vault!" + }, + "hasItemsVaultNudgeBody": { + "message": "Autofill items for the current page\nFavorite items for easy access\nSearch your vault for something else" + }, + "newLoginNudgeTitle": { + "message": "Save time with autofill" + }, + "newLoginNudgeBody": { + "message": "Include a Website so this login appears as an autofill suggestion." + }, + "newCardNudgeTitle": { + "message": "Seamless online checkout" + }, + "newCardNudgeBody": { + "message": "With cards, easily autofill payment forms securely and accurately." + }, + "newIdentityNudgeTitle": { + "message": "Simplify creating accounts" + }, + "newIdentityNudgeBody": { + "message": "With identities, quickly autofill long registration or contact forms." + }, + "newNoteNudgeTitle": { + "message": "Keep your sensitive data safe" + }, + "newNoteNudgeBody": { + "message": "With notes, securely store sensitive data like banking or insurance details." + }, + "newSshNudgeTitle": { + "message": "Developer-friendly SSH access" + }, + "newSshNudgeBody": { + "message": "Store your keys and connect with the SSH agent for fast, encrypted authentication." } } diff --git a/apps/browser/src/_locales/kn/messages.json b/apps/browser/src/_locales/kn/messages.json index 7d9583dc652..408727f7ec8 100644 --- a/apps/browser/src/_locales/kn/messages.json +++ b/apps/browser/src/_locales/kn/messages.json @@ -2,6 +2,9 @@ "appName": { "message": "ಬಿಟ್ವಾರ್ಡೆನ್" }, + "appLogoLabel": { + "message": "Bitwarden logo" + }, "extName": { "message": "Bitwarden Password Manager", "description": "Extension name, MUST be less than 40 characters (Safari restriction)" @@ -883,6 +886,9 @@ "followTheStepsBelowToFinishLoggingIn": { "message": "Follow the steps below to finish logging in." }, + "followTheStepsBelowToFinishLoggingInWithSecurityKey": { + "message": "Follow the steps below to finish logging in with your security key." + }, "restartRegistration": { "message": "Restart registration" }, @@ -1056,23 +1062,49 @@ "notificationAddSave": { "message": "ಹೌದು, ಈಗ ಉಳಿಸಿ" }, - "loginSaveSuccessDetails": { - "message": "$USERNAME$ saved to Bitwarden.", + "notificationViewAria": { + "message": "View $ITEMNAME$, opens in new window", "placeholders": { - "username": { + "itemName": { "content": "$1" } }, - "description": "Shown to user after login is saved." + "description": "Aria label for the view button in notification bar confirmation message" }, - "loginUpdatedSuccessDetails": { - "message": "$USERNAME$ updated in Bitwarden.", + "notificationEditTooltip": { + "message": "Edit before saving", + "description": "Tooltip and Aria label for edit button on cipher item" + }, + "newNotification": { + "message": "New notification" + }, + "labelWithNotification": { + "message": "$LABEL$: New notification", + "description": "Label for the notification with a new login suggestion.", "placeholders": { - "username": { + "label": { + "content": "$1", + "example": "Login" + } + } + }, + "loginSaveConfirmation": { + "message": "$ITEMNAME$ saved to Bitwarden.", + "placeholders": { + "itemName": { "content": "$1" } }, - "description": "Shown to user after login is updated." + "description": "Shown to user after item is saved." + }, + "loginUpdatedConfirmation": { + "message": "$ITEMNAME$ updated in Bitwarden.", + "placeholders": { + "itemName": { + "content": "$1" + } + }, + "description": "Shown to user after item is updated." }, "saveAsNewLoginAction": { "message": "Save as new login", @@ -1082,12 +1114,12 @@ "message": "Update login", "description": "Button text for updating an existing login entry." }, - "saveLoginPrompt": { - "message": "Save login?", + "saveLogin": { + "message": "Save login", "description": "Prompt asking the user if they want to save their login details." }, - "updateLoginPrompt": { - "message": "Update existing login?", + "updateLogin": { + "message": "Update existing login", "description": "Prompt asking the user if they want to update an existing login entry." }, "loginSaveSuccess": { @@ -4928,8 +4960,8 @@ "message": "Password regenerated", "description": "Notification message for when a password has been regenerated" }, - "saveLoginToBitwarden": { - "message": "Save login to Bitwarden?", + "saveToBitwarden": { + "message": "Save to Bitwarden", "description": "Confirmation message for saving a login to Bitwarden" }, "spaceCharacterDescriptor": { @@ -5169,6 +5201,12 @@ "changeAtRiskPassword": { "message": "Change at-risk password" }, + "settingsVaultOptions": { + "message": "Vault options" + }, + "emptyVaultDescription": { + "message": "The vault protects more than just your passwords. Store secure logins, IDs, cards and notes securely here." + }, "introCarouselLabel": { "message": "Welcome to Bitwarden" }, @@ -5195,5 +5233,50 @@ }, "secureDevicesBody": { "message": "Save unlimited passwords across unlimited devices with Bitwarden mobile, browser, and desktop apps." + }, + "emptyVaultNudgeTitle": { + "message": "Import existing passwords" + }, + "emptyVaultNudgeBody": { + "message": "Use the importer to quickly transfer logins to Bitwarden without manually adding them." + }, + "emptyVaultNudgeButton": { + "message": "Import now" + }, + "hasItemsVaultNudgeTitle": { + "message": "Welcome to your vault!" + }, + "hasItemsVaultNudgeBody": { + "message": "Autofill items for the current page\nFavorite items for easy access\nSearch your vault for something else" + }, + "newLoginNudgeTitle": { + "message": "Save time with autofill" + }, + "newLoginNudgeBody": { + "message": "Include a Website so this login appears as an autofill suggestion." + }, + "newCardNudgeTitle": { + "message": "Seamless online checkout" + }, + "newCardNudgeBody": { + "message": "With cards, easily autofill payment forms securely and accurately." + }, + "newIdentityNudgeTitle": { + "message": "Simplify creating accounts" + }, + "newIdentityNudgeBody": { + "message": "With identities, quickly autofill long registration or contact forms." + }, + "newNoteNudgeTitle": { + "message": "Keep your sensitive data safe" + }, + "newNoteNudgeBody": { + "message": "With notes, securely store sensitive data like banking or insurance details." + }, + "newSshNudgeTitle": { + "message": "Developer-friendly SSH access" + }, + "newSshNudgeBody": { + "message": "Store your keys and connect with the SSH agent for fast, encrypted authentication." } } diff --git a/apps/browser/src/_locales/ko/messages.json b/apps/browser/src/_locales/ko/messages.json index 7d65686df5d..01cb0fbf7c3 100644 --- a/apps/browser/src/_locales/ko/messages.json +++ b/apps/browser/src/_locales/ko/messages.json @@ -2,6 +2,9 @@ "appName": { "message": "Bitwarden" }, + "appLogoLabel": { + "message": "Bitwarden logo" + }, "extName": { "message": "Bitwarden 비밀번호 관리자", "description": "Extension name, MUST be less than 40 characters (Safari restriction)" @@ -883,6 +886,9 @@ "followTheStepsBelowToFinishLoggingIn": { "message": "Follow the steps below to finish logging in." }, + "followTheStepsBelowToFinishLoggingInWithSecurityKey": { + "message": "Follow the steps below to finish logging in with your security key." + }, "restartRegistration": { "message": "등록 재시작" }, @@ -1056,23 +1062,49 @@ "notificationAddSave": { "message": "예, 지금 저장하겠습니다." }, - "loginSaveSuccessDetails": { - "message": "$USERNAME$ saved to Bitwarden.", + "notificationViewAria": { + "message": "View $ITEMNAME$, opens in new window", "placeholders": { - "username": { + "itemName": { "content": "$1" } }, - "description": "Shown to user after login is saved." + "description": "Aria label for the view button in notification bar confirmation message" }, - "loginUpdatedSuccessDetails": { - "message": "$USERNAME$ updated in Bitwarden.", + "notificationEditTooltip": { + "message": "Edit before saving", + "description": "Tooltip and Aria label for edit button on cipher item" + }, + "newNotification": { + "message": "New notification" + }, + "labelWithNotification": { + "message": "$LABEL$: New notification", + "description": "Label for the notification with a new login suggestion.", "placeholders": { - "username": { + "label": { + "content": "$1", + "example": "Login" + } + } + }, + "loginSaveConfirmation": { + "message": "$ITEMNAME$ saved to Bitwarden.", + "placeholders": { + "itemName": { "content": "$1" } }, - "description": "Shown to user after login is updated." + "description": "Shown to user after item is saved." + }, + "loginUpdatedConfirmation": { + "message": "$ITEMNAME$ updated in Bitwarden.", + "placeholders": { + "itemName": { + "content": "$1" + } + }, + "description": "Shown to user after item is updated." }, "saveAsNewLoginAction": { "message": "Save as new login", @@ -1082,12 +1114,12 @@ "message": "Update login", "description": "Button text for updating an existing login entry." }, - "saveLoginPrompt": { - "message": "Save login?", + "saveLogin": { + "message": "Save login", "description": "Prompt asking the user if they want to save their login details." }, - "updateLoginPrompt": { - "message": "Update existing login?", + "updateLogin": { + "message": "Update existing login", "description": "Prompt asking the user if they want to update an existing login entry." }, "loginSaveSuccess": { @@ -4928,8 +4960,8 @@ "message": "비밀번호가 재생성되었습니다.", "description": "Notification message for when a password has been regenerated" }, - "saveLoginToBitwarden": { - "message": "Bitwarden에 로그인을 저장하시겠습니까?", + "saveToBitwarden": { + "message": "Save to Bitwarden", "description": "Confirmation message for saving a login to Bitwarden" }, "spaceCharacterDescriptor": { @@ -5169,6 +5201,12 @@ "changeAtRiskPassword": { "message": "Change at-risk password" }, + "settingsVaultOptions": { + "message": "Vault options" + }, + "emptyVaultDescription": { + "message": "The vault protects more than just your passwords. Store secure logins, IDs, cards and notes securely here." + }, "introCarouselLabel": { "message": "Welcome to Bitwarden" }, @@ -5195,5 +5233,50 @@ }, "secureDevicesBody": { "message": "Save unlimited passwords across unlimited devices with Bitwarden mobile, browser, and desktop apps." + }, + "emptyVaultNudgeTitle": { + "message": "Import existing passwords" + }, + "emptyVaultNudgeBody": { + "message": "Use the importer to quickly transfer logins to Bitwarden without manually adding them." + }, + "emptyVaultNudgeButton": { + "message": "Import now" + }, + "hasItemsVaultNudgeTitle": { + "message": "Welcome to your vault!" + }, + "hasItemsVaultNudgeBody": { + "message": "Autofill items for the current page\nFavorite items for easy access\nSearch your vault for something else" + }, + "newLoginNudgeTitle": { + "message": "Save time with autofill" + }, + "newLoginNudgeBody": { + "message": "Include a Website so this login appears as an autofill suggestion." + }, + "newCardNudgeTitle": { + "message": "Seamless online checkout" + }, + "newCardNudgeBody": { + "message": "With cards, easily autofill payment forms securely and accurately." + }, + "newIdentityNudgeTitle": { + "message": "Simplify creating accounts" + }, + "newIdentityNudgeBody": { + "message": "With identities, quickly autofill long registration or contact forms." + }, + "newNoteNudgeTitle": { + "message": "Keep your sensitive data safe" + }, + "newNoteNudgeBody": { + "message": "With notes, securely store sensitive data like banking or insurance details." + }, + "newSshNudgeTitle": { + "message": "Developer-friendly SSH access" + }, + "newSshNudgeBody": { + "message": "Store your keys and connect with the SSH agent for fast, encrypted authentication." } } diff --git a/apps/browser/src/_locales/lt/messages.json b/apps/browser/src/_locales/lt/messages.json index 2245fdb0029..51879334614 100644 --- a/apps/browser/src/_locales/lt/messages.json +++ b/apps/browser/src/_locales/lt/messages.json @@ -2,6 +2,9 @@ "appName": { "message": "Bitwarden" }, + "appLogoLabel": { + "message": "Bitwarden logo" + }, "extName": { "message": "„Bitwarden“ slaptažodžių tvarkyklė", "description": "Extension name, MUST be less than 40 characters (Safari restriction)" @@ -883,6 +886,9 @@ "followTheStepsBelowToFinishLoggingIn": { "message": "Follow the steps below to finish logging in." }, + "followTheStepsBelowToFinishLoggingInWithSecurityKey": { + "message": "Follow the steps below to finish logging in with your security key." + }, "restartRegistration": { "message": "Restart registration" }, @@ -1056,23 +1062,49 @@ "notificationAddSave": { "message": "Išsaugoti" }, - "loginSaveSuccessDetails": { - "message": "$USERNAME$ saved to Bitwarden.", + "notificationViewAria": { + "message": "View $ITEMNAME$, opens in new window", "placeholders": { - "username": { + "itemName": { "content": "$1" } }, - "description": "Shown to user after login is saved." + "description": "Aria label for the view button in notification bar confirmation message" }, - "loginUpdatedSuccessDetails": { - "message": "$USERNAME$ updated in Bitwarden.", + "notificationEditTooltip": { + "message": "Edit before saving", + "description": "Tooltip and Aria label for edit button on cipher item" + }, + "newNotification": { + "message": "New notification" + }, + "labelWithNotification": { + "message": "$LABEL$: New notification", + "description": "Label for the notification with a new login suggestion.", "placeholders": { - "username": { + "label": { + "content": "$1", + "example": "Login" + } + } + }, + "loginSaveConfirmation": { + "message": "$ITEMNAME$ saved to Bitwarden.", + "placeholders": { + "itemName": { "content": "$1" } }, - "description": "Shown to user after login is updated." + "description": "Shown to user after item is saved." + }, + "loginUpdatedConfirmation": { + "message": "$ITEMNAME$ updated in Bitwarden.", + "placeholders": { + "itemName": { + "content": "$1" + } + }, + "description": "Shown to user after item is updated." }, "saveAsNewLoginAction": { "message": "Save as new login", @@ -1082,12 +1114,12 @@ "message": "Update login", "description": "Button text for updating an existing login entry." }, - "saveLoginPrompt": { - "message": "Save login?", + "saveLogin": { + "message": "Save login", "description": "Prompt asking the user if they want to save their login details." }, - "updateLoginPrompt": { - "message": "Update existing login?", + "updateLogin": { + "message": "Update existing login", "description": "Prompt asking the user if they want to update an existing login entry." }, "loginSaveSuccess": { @@ -4928,8 +4960,8 @@ "message": "Password regenerated", "description": "Notification message for when a password has been regenerated" }, - "saveLoginToBitwarden": { - "message": "Save login to Bitwarden?", + "saveToBitwarden": { + "message": "Save to Bitwarden", "description": "Confirmation message for saving a login to Bitwarden" }, "spaceCharacterDescriptor": { @@ -5169,6 +5201,12 @@ "changeAtRiskPassword": { "message": "Change at-risk password" }, + "settingsVaultOptions": { + "message": "Vault options" + }, + "emptyVaultDescription": { + "message": "The vault protects more than just your passwords. Store secure logins, IDs, cards and notes securely here." + }, "introCarouselLabel": { "message": "Welcome to Bitwarden" }, @@ -5195,5 +5233,50 @@ }, "secureDevicesBody": { "message": "Save unlimited passwords across unlimited devices with Bitwarden mobile, browser, and desktop apps." + }, + "emptyVaultNudgeTitle": { + "message": "Import existing passwords" + }, + "emptyVaultNudgeBody": { + "message": "Use the importer to quickly transfer logins to Bitwarden without manually adding them." + }, + "emptyVaultNudgeButton": { + "message": "Import now" + }, + "hasItemsVaultNudgeTitle": { + "message": "Welcome to your vault!" + }, + "hasItemsVaultNudgeBody": { + "message": "Autofill items for the current page\nFavorite items for easy access\nSearch your vault for something else" + }, + "newLoginNudgeTitle": { + "message": "Save time with autofill" + }, + "newLoginNudgeBody": { + "message": "Include a Website so this login appears as an autofill suggestion." + }, + "newCardNudgeTitle": { + "message": "Seamless online checkout" + }, + "newCardNudgeBody": { + "message": "With cards, easily autofill payment forms securely and accurately." + }, + "newIdentityNudgeTitle": { + "message": "Simplify creating accounts" + }, + "newIdentityNudgeBody": { + "message": "With identities, quickly autofill long registration or contact forms." + }, + "newNoteNudgeTitle": { + "message": "Keep your sensitive data safe" + }, + "newNoteNudgeBody": { + "message": "With notes, securely store sensitive data like banking or insurance details." + }, + "newSshNudgeTitle": { + "message": "Developer-friendly SSH access" + }, + "newSshNudgeBody": { + "message": "Store your keys and connect with the SSH agent for fast, encrypted authentication." } } diff --git a/apps/browser/src/_locales/lv/messages.json b/apps/browser/src/_locales/lv/messages.json index 517dacfb72b..6c39d9ff3e6 100644 --- a/apps/browser/src/_locales/lv/messages.json +++ b/apps/browser/src/_locales/lv/messages.json @@ -2,6 +2,9 @@ "appName": { "message": "Bitwarden" }, + "appLogoLabel": { + "message": "Bitwarden logotips" + }, "extName": { "message": "Bitwarden paroļu pārvaldnieks", "description": "Extension name, MUST be less than 40 characters (Safari restriction)" @@ -434,7 +437,7 @@ "message": "Sinhronizēt glabātavu" }, "lastSync": { - "message": "Pēdējā sinhronizācija:" + "message": "Pēdējā sinhronizēšana:" }, "passGen": { "message": "Paroļu veidotājs" @@ -883,6 +886,9 @@ "followTheStepsBelowToFinishLoggingIn": { "message": "Jāseko zemāk esošajām norādēm, lai pabeigtu pieteikšanos." }, + "followTheStepsBelowToFinishLoggingInWithSecurityKey": { + "message": "Jāizpilda zemāk esošās darbības, lai pabeigtu pieteikšanos ar savu drošības atslēgu." + }, "restartRegistration": { "message": "Sākt reģistrēšanos no jauna" }, @@ -941,10 +947,10 @@ "message": "Noskaties mūsu uzsākšanas pamācību, lai uzzinātu, kā iegūt vislielāko labumu no pārlūka paplašinājuma!" }, "syncingComplete": { - "message": "Sinhronizācija pabeigta" + "message": "Sinhronizēšana pabeigta" }, "syncingFailed": { - "message": "Sinhronizācija neizdevās" + "message": "Sinhronizēšana neizdevās" }, "passwordCopied": { "message": "Parole ievietota starpliktuvē" @@ -1056,23 +1062,49 @@ "notificationAddSave": { "message": "Jā, saglabāt" }, - "loginSaveSuccessDetails": { - "message": "$USERNAME$ saglabāts Bitwarden.", + "notificationViewAria": { + "message": "Apskatīt $ITEMNAME$, tiks atvērts jaunā logā", "placeholders": { - "username": { + "itemName": { "content": "$1" } }, - "description": "Shown to user after login is saved." + "description": "Aria label for the view button in notification bar confirmation message" }, - "loginUpdatedSuccessDetails": { - "message": "$USERNAME$ atjaunināts Bitwarden.", + "notificationEditTooltip": { + "message": "Labot pirms saglabāšanas", + "description": "Tooltip and Aria label for edit button on cipher item" + }, + "newNotification": { + "message": "Jauns paziņojums" + }, + "labelWithNotification": { + "message": "$LABEL$: jauns paziņojums", + "description": "Label for the notification with a new login suggestion.", "placeholders": { - "username": { + "label": { + "content": "$1", + "example": "Login" + } + } + }, + "loginSaveConfirmation": { + "message": "$ITEMNAME$ saglabāts Bitwarden.", + "placeholders": { + "itemName": { "content": "$1" } }, - "description": "Shown to user after login is updated." + "description": "Shown to user after item is saved." + }, + "loginUpdatedConfirmation": { + "message": "$ITEMNAME$ atjaunināts Bitwarden.", + "placeholders": { + "itemName": { + "content": "$1" + } + }, + "description": "Shown to user after item is updated." }, "saveAsNewLoginAction": { "message": "Saglabāt kā jaunu pieteikšanās vienumu", @@ -1082,12 +1114,12 @@ "message": "Atjaunināt pieteikšanās vienumu", "description": "Button text for updating an existing login entry." }, - "saveLoginPrompt": { - "message": "Saglabāt pieteikšanās vienumu?", + "saveLogin": { + "message": "Saglabāt pieteikšanās vienumu", "description": "Prompt asking the user if they want to save their login details." }, - "updateLoginPrompt": { - "message": "Atjaunināt esošo pieteikšanās vienumu?", + "updateLogin": { + "message": "Atjaunināt esošo pieteikšanās vienumu", "description": "Prompt asking the user if they want to update an existing login entry." }, "loginSaveSuccess": { @@ -1511,7 +1543,7 @@ "message": "Jāievada e-pastā nosūtītais kods." }, "selfHostedEnvironment": { - "message": "Pašuzturēta vide" + "message": "Pašmitināta vide" }, "selfHostedBaseUrlHint": { "message": "Jānorāda sava pašizvietotā Bitward servera pamata URL. Piemērs: https://bitwarden.uznemums.lv" @@ -2338,7 +2370,7 @@ "message": "Netika atrastas atsvaidzināšanas pilnvaras vai API atslēgas. Lūgums mēģināt izrakstīties un atkal pieteikties." }, "desktopSyncVerificationTitle": { - "message": "Darbvirsmas sinhronizācijas apstiprinājums" + "message": "Darbvirsmas sinhronizēšanas apliecinājums" }, "desktopIntegrationVerificationText": { "message": "Lūgumus pārliecināties, ka darbvirsmas lietotne rāda šo atpazīšanas vārdkopu:" @@ -2964,7 +2996,7 @@ "message": "Nav atrasts neviens neatkārtojams identifikators" }, "convertOrganizationEncryptionDesc": { - "message": "$ORGANIZATION$ izmanto vienoto pieteikšanos ar pašizvietotu atslēgu serveri. Tās dalībniekiem vairs nav nepieciešama galvenā parole, lai pieteiktos.", + "message": "$ORGANIZATION$ izmanto vienoto pieteikšanos ar pašmitinātu atslēgu serveri. Tās dalībniekiem vairs nav nepieciešama galvenā parole, lai pieteiktos.", "placeholders": { "organization": { "content": "$1", @@ -3283,7 +3315,7 @@ "message": "Servera versija" }, "selfHostedServer": { - "message": "pašizvietots" + "message": "pašmitināts" }, "thirdParty": { "message": "Trešās puses" @@ -4928,8 +4960,8 @@ "message": "Parole pārizveidota", "description": "Notification message for when a password has been regenerated" }, - "saveLoginToBitwarden": { - "message": "Saglabāt pieteikšanās vienumu Bitwarden?", + "saveToBitwarden": { + "message": "Saglabāt Bitwarden", "description": "Confirmation message for saving a login to Bitwarden" }, "spaceCharacterDescriptor": { @@ -5169,6 +5201,12 @@ "changeAtRiskPassword": { "message": "Mainīt riskam pakļautu paroli" }, + "settingsVaultOptions": { + "message": "Glabātavas iespējas" + }, + "emptyVaultDescription": { + "message": "Glabātava aizsargā vairāk kā tikai paroles. Drošā veidā glabā šeit pieteikšanās vienumus, identitātes, kartes un piezīmes!" + }, "introCarouselLabel": { "message": "Laipni lūdzam Bitwarden" }, @@ -5195,5 +5233,50 @@ }, "secureDevicesBody": { "message": "Neierobežotu paroļu skaitu var saglabāt neierobežotā ierīdžu daudzumā ar Bitwarden viedtālruņa, pārlūka un darbvirsmas lietotni." + }, + "emptyVaultNudgeTitle": { + "message": "Ievietot esošas paroles" + }, + "emptyVaultNudgeBody": { + "message": "Ievietotājs ir izmantojams, lai pieteikšanās vienumus ātri pārnest uz Bitwarden bez pašrocīgas to pievienošanas." + }, + "emptyVaultNudgeButton": { + "message": "Ievietot tagad" + }, + "hasItemsVaultNudgeTitle": { + "message": "Laipni lūdzam Tavā glabātavā!" + }, + "hasItemsVaultNudgeBody": { + "message": "Automātiskās aizpldes vienumu pašreizējai lapai\nIzlases vienumi vieglai piekļuvei\nPārējo var meklēt glabātavā" + }, + "newLoginNudgeTitle": { + "message": "Laika ietaupīšana ar automātisko aizpildi" + }, + "newLoginNudgeBody": { + "message": "Iekļauj tīmekļvietni, lai šis pieteikšanās vienums parādītos kā automātiskās aizpildes ieteikums!" + }, + "newCardNudgeTitle": { + "message": "Plūdena apmaksa tiešsaistē" + }, + "newCardNudgeBody": { + "message": "Ar kartēm ir viegli automātiski aizpildīt maksājumu veidlapu droši un rūpīgi." + }, + "newIdentityNudgeTitle": { + "message": "Kontu izveidošanas vienkāršošana" + }, + "newIdentityNudgeBody": { + "message": "Ar identitātēm var ātri automātiski aizpildīt garas reģistrēšanās vai saziņas veidlapas." + }, + "newNoteNudgeTitle": { + "message": "Turi savus jūtīgos datus drošībā" + }, + "newNoteNudgeBody": { + "message": "Piezīmēs var droši glabāt jūtīgus datus, piemēram, bankas vai apdrošināšanas informāciju." + }, + "newSshNudgeTitle": { + "message": "Izstrādātājiem draudzīga SSH piekļuve" + }, + "newSshNudgeBody": { + "message": "Glabā savas atslēgas un savienojies ar SSH aģentu ātrai, šifrētai autentificēšanai!" } } diff --git a/apps/browser/src/_locales/ml/messages.json b/apps/browser/src/_locales/ml/messages.json index 49a2695ce6f..15d35d29c76 100644 --- a/apps/browser/src/_locales/ml/messages.json +++ b/apps/browser/src/_locales/ml/messages.json @@ -2,6 +2,9 @@ "appName": { "message": "Bitwarden" }, + "appLogoLabel": { + "message": "Bitwarden logo" + }, "extName": { "message": "Bitwarden Password Manager", "description": "Extension name, MUST be less than 40 characters (Safari restriction)" @@ -883,6 +886,9 @@ "followTheStepsBelowToFinishLoggingIn": { "message": "Follow the steps below to finish logging in." }, + "followTheStepsBelowToFinishLoggingInWithSecurityKey": { + "message": "Follow the steps below to finish logging in with your security key." + }, "restartRegistration": { "message": "Restart registration" }, @@ -1056,23 +1062,49 @@ "notificationAddSave": { "message": "ശരി, ഇപ്പോൾ സംരക്ഷിക്കുക" }, - "loginSaveSuccessDetails": { - "message": "$USERNAME$ saved to Bitwarden.", + "notificationViewAria": { + "message": "View $ITEMNAME$, opens in new window", "placeholders": { - "username": { + "itemName": { "content": "$1" } }, - "description": "Shown to user after login is saved." + "description": "Aria label for the view button in notification bar confirmation message" }, - "loginUpdatedSuccessDetails": { - "message": "$USERNAME$ updated in Bitwarden.", + "notificationEditTooltip": { + "message": "Edit before saving", + "description": "Tooltip and Aria label for edit button on cipher item" + }, + "newNotification": { + "message": "New notification" + }, + "labelWithNotification": { + "message": "$LABEL$: New notification", + "description": "Label for the notification with a new login suggestion.", "placeholders": { - "username": { + "label": { + "content": "$1", + "example": "Login" + } + } + }, + "loginSaveConfirmation": { + "message": "$ITEMNAME$ saved to Bitwarden.", + "placeholders": { + "itemName": { "content": "$1" } }, - "description": "Shown to user after login is updated." + "description": "Shown to user after item is saved." + }, + "loginUpdatedConfirmation": { + "message": "$ITEMNAME$ updated in Bitwarden.", + "placeholders": { + "itemName": { + "content": "$1" + } + }, + "description": "Shown to user after item is updated." }, "saveAsNewLoginAction": { "message": "Save as new login", @@ -1082,12 +1114,12 @@ "message": "Update login", "description": "Button text for updating an existing login entry." }, - "saveLoginPrompt": { - "message": "Save login?", + "saveLogin": { + "message": "Save login", "description": "Prompt asking the user if they want to save their login details." }, - "updateLoginPrompt": { - "message": "Update existing login?", + "updateLogin": { + "message": "Update existing login", "description": "Prompt asking the user if they want to update an existing login entry." }, "loginSaveSuccess": { @@ -4928,8 +4960,8 @@ "message": "Password regenerated", "description": "Notification message for when a password has been regenerated" }, - "saveLoginToBitwarden": { - "message": "Save login to Bitwarden?", + "saveToBitwarden": { + "message": "Save to Bitwarden", "description": "Confirmation message for saving a login to Bitwarden" }, "spaceCharacterDescriptor": { @@ -5169,6 +5201,12 @@ "changeAtRiskPassword": { "message": "Change at-risk password" }, + "settingsVaultOptions": { + "message": "Vault options" + }, + "emptyVaultDescription": { + "message": "The vault protects more than just your passwords. Store secure logins, IDs, cards and notes securely here." + }, "introCarouselLabel": { "message": "Welcome to Bitwarden" }, @@ -5195,5 +5233,50 @@ }, "secureDevicesBody": { "message": "Save unlimited passwords across unlimited devices with Bitwarden mobile, browser, and desktop apps." + }, + "emptyVaultNudgeTitle": { + "message": "Import existing passwords" + }, + "emptyVaultNudgeBody": { + "message": "Use the importer to quickly transfer logins to Bitwarden without manually adding them." + }, + "emptyVaultNudgeButton": { + "message": "Import now" + }, + "hasItemsVaultNudgeTitle": { + "message": "Welcome to your vault!" + }, + "hasItemsVaultNudgeBody": { + "message": "Autofill items for the current page\nFavorite items for easy access\nSearch your vault for something else" + }, + "newLoginNudgeTitle": { + "message": "Save time with autofill" + }, + "newLoginNudgeBody": { + "message": "Include a Website so this login appears as an autofill suggestion." + }, + "newCardNudgeTitle": { + "message": "Seamless online checkout" + }, + "newCardNudgeBody": { + "message": "With cards, easily autofill payment forms securely and accurately." + }, + "newIdentityNudgeTitle": { + "message": "Simplify creating accounts" + }, + "newIdentityNudgeBody": { + "message": "With identities, quickly autofill long registration or contact forms." + }, + "newNoteNudgeTitle": { + "message": "Keep your sensitive data safe" + }, + "newNoteNudgeBody": { + "message": "With notes, securely store sensitive data like banking or insurance details." + }, + "newSshNudgeTitle": { + "message": "Developer-friendly SSH access" + }, + "newSshNudgeBody": { + "message": "Store your keys and connect with the SSH agent for fast, encrypted authentication." } } diff --git a/apps/browser/src/_locales/mr/messages.json b/apps/browser/src/_locales/mr/messages.json index 6b2f7b8bc32..8dee37bfcb8 100644 --- a/apps/browser/src/_locales/mr/messages.json +++ b/apps/browser/src/_locales/mr/messages.json @@ -2,6 +2,9 @@ "appName": { "message": "Bitwarden" }, + "appLogoLabel": { + "message": "Bitwarden logo" + }, "extName": { "message": "Bitwarden Password Manager", "description": "Extension name, MUST be less than 40 characters (Safari restriction)" @@ -883,6 +886,9 @@ "followTheStepsBelowToFinishLoggingIn": { "message": "Follow the steps below to finish logging in." }, + "followTheStepsBelowToFinishLoggingInWithSecurityKey": { + "message": "Follow the steps below to finish logging in with your security key." + }, "restartRegistration": { "message": "Restart registration" }, @@ -1056,23 +1062,49 @@ "notificationAddSave": { "message": "Save" }, - "loginSaveSuccessDetails": { - "message": "$USERNAME$ saved to Bitwarden.", + "notificationViewAria": { + "message": "View $ITEMNAME$, opens in new window", "placeholders": { - "username": { + "itemName": { "content": "$1" } }, - "description": "Shown to user after login is saved." + "description": "Aria label for the view button in notification bar confirmation message" }, - "loginUpdatedSuccessDetails": { - "message": "$USERNAME$ updated in Bitwarden.", + "notificationEditTooltip": { + "message": "Edit before saving", + "description": "Tooltip and Aria label for edit button on cipher item" + }, + "newNotification": { + "message": "New notification" + }, + "labelWithNotification": { + "message": "$LABEL$: New notification", + "description": "Label for the notification with a new login suggestion.", "placeholders": { - "username": { + "label": { + "content": "$1", + "example": "Login" + } + } + }, + "loginSaveConfirmation": { + "message": "$ITEMNAME$ saved to Bitwarden.", + "placeholders": { + "itemName": { "content": "$1" } }, - "description": "Shown to user after login is updated." + "description": "Shown to user after item is saved." + }, + "loginUpdatedConfirmation": { + "message": "$ITEMNAME$ updated in Bitwarden.", + "placeholders": { + "itemName": { + "content": "$1" + } + }, + "description": "Shown to user after item is updated." }, "saveAsNewLoginAction": { "message": "Save as new login", @@ -1082,12 +1114,12 @@ "message": "Update login", "description": "Button text for updating an existing login entry." }, - "saveLoginPrompt": { - "message": "Save login?", + "saveLogin": { + "message": "Save login", "description": "Prompt asking the user if they want to save their login details." }, - "updateLoginPrompt": { - "message": "Update existing login?", + "updateLogin": { + "message": "Update existing login", "description": "Prompt asking the user if they want to update an existing login entry." }, "loginSaveSuccess": { @@ -4928,8 +4960,8 @@ "message": "Password regenerated", "description": "Notification message for when a password has been regenerated" }, - "saveLoginToBitwarden": { - "message": "Save login to Bitwarden?", + "saveToBitwarden": { + "message": "Save to Bitwarden", "description": "Confirmation message for saving a login to Bitwarden" }, "spaceCharacterDescriptor": { @@ -5169,6 +5201,12 @@ "changeAtRiskPassword": { "message": "Change at-risk password" }, + "settingsVaultOptions": { + "message": "Vault options" + }, + "emptyVaultDescription": { + "message": "The vault protects more than just your passwords. Store secure logins, IDs, cards and notes securely here." + }, "introCarouselLabel": { "message": "Welcome to Bitwarden" }, @@ -5195,5 +5233,50 @@ }, "secureDevicesBody": { "message": "Save unlimited passwords across unlimited devices with Bitwarden mobile, browser, and desktop apps." + }, + "emptyVaultNudgeTitle": { + "message": "Import existing passwords" + }, + "emptyVaultNudgeBody": { + "message": "Use the importer to quickly transfer logins to Bitwarden without manually adding them." + }, + "emptyVaultNudgeButton": { + "message": "Import now" + }, + "hasItemsVaultNudgeTitle": { + "message": "Welcome to your vault!" + }, + "hasItemsVaultNudgeBody": { + "message": "Autofill items for the current page\nFavorite items for easy access\nSearch your vault for something else" + }, + "newLoginNudgeTitle": { + "message": "Save time with autofill" + }, + "newLoginNudgeBody": { + "message": "Include a Website so this login appears as an autofill suggestion." + }, + "newCardNudgeTitle": { + "message": "Seamless online checkout" + }, + "newCardNudgeBody": { + "message": "With cards, easily autofill payment forms securely and accurately." + }, + "newIdentityNudgeTitle": { + "message": "Simplify creating accounts" + }, + "newIdentityNudgeBody": { + "message": "With identities, quickly autofill long registration or contact forms." + }, + "newNoteNudgeTitle": { + "message": "Keep your sensitive data safe" + }, + "newNoteNudgeBody": { + "message": "With notes, securely store sensitive data like banking or insurance details." + }, + "newSshNudgeTitle": { + "message": "Developer-friendly SSH access" + }, + "newSshNudgeBody": { + "message": "Store your keys and connect with the SSH agent for fast, encrypted authentication." } } diff --git a/apps/browser/src/_locales/my/messages.json b/apps/browser/src/_locales/my/messages.json index 7bec167e1c6..99a09c75d41 100644 --- a/apps/browser/src/_locales/my/messages.json +++ b/apps/browser/src/_locales/my/messages.json @@ -2,6 +2,9 @@ "appName": { "message": "Bitwarden" }, + "appLogoLabel": { + "message": "Bitwarden logo" + }, "extName": { "message": "Bitwarden Password Manager", "description": "Extension name, MUST be less than 40 characters (Safari restriction)" @@ -883,6 +886,9 @@ "followTheStepsBelowToFinishLoggingIn": { "message": "Follow the steps below to finish logging in." }, + "followTheStepsBelowToFinishLoggingInWithSecurityKey": { + "message": "Follow the steps below to finish logging in with your security key." + }, "restartRegistration": { "message": "Restart registration" }, @@ -1056,23 +1062,49 @@ "notificationAddSave": { "message": "Save" }, - "loginSaveSuccessDetails": { - "message": "$USERNAME$ saved to Bitwarden.", + "notificationViewAria": { + "message": "View $ITEMNAME$, opens in new window", "placeholders": { - "username": { + "itemName": { "content": "$1" } }, - "description": "Shown to user after login is saved." + "description": "Aria label for the view button in notification bar confirmation message" }, - "loginUpdatedSuccessDetails": { - "message": "$USERNAME$ updated in Bitwarden.", + "notificationEditTooltip": { + "message": "Edit before saving", + "description": "Tooltip and Aria label for edit button on cipher item" + }, + "newNotification": { + "message": "New notification" + }, + "labelWithNotification": { + "message": "$LABEL$: New notification", + "description": "Label for the notification with a new login suggestion.", "placeholders": { - "username": { + "label": { + "content": "$1", + "example": "Login" + } + } + }, + "loginSaveConfirmation": { + "message": "$ITEMNAME$ saved to Bitwarden.", + "placeholders": { + "itemName": { "content": "$1" } }, - "description": "Shown to user after login is updated." + "description": "Shown to user after item is saved." + }, + "loginUpdatedConfirmation": { + "message": "$ITEMNAME$ updated in Bitwarden.", + "placeholders": { + "itemName": { + "content": "$1" + } + }, + "description": "Shown to user after item is updated." }, "saveAsNewLoginAction": { "message": "Save as new login", @@ -1082,12 +1114,12 @@ "message": "Update login", "description": "Button text for updating an existing login entry." }, - "saveLoginPrompt": { - "message": "Save login?", + "saveLogin": { + "message": "Save login", "description": "Prompt asking the user if they want to save their login details." }, - "updateLoginPrompt": { - "message": "Update existing login?", + "updateLogin": { + "message": "Update existing login", "description": "Prompt asking the user if they want to update an existing login entry." }, "loginSaveSuccess": { @@ -4928,8 +4960,8 @@ "message": "Password regenerated", "description": "Notification message for when a password has been regenerated" }, - "saveLoginToBitwarden": { - "message": "Save login to Bitwarden?", + "saveToBitwarden": { + "message": "Save to Bitwarden", "description": "Confirmation message for saving a login to Bitwarden" }, "spaceCharacterDescriptor": { @@ -5169,6 +5201,12 @@ "changeAtRiskPassword": { "message": "Change at-risk password" }, + "settingsVaultOptions": { + "message": "Vault options" + }, + "emptyVaultDescription": { + "message": "The vault protects more than just your passwords. Store secure logins, IDs, cards and notes securely here." + }, "introCarouselLabel": { "message": "Welcome to Bitwarden" }, @@ -5195,5 +5233,50 @@ }, "secureDevicesBody": { "message": "Save unlimited passwords across unlimited devices with Bitwarden mobile, browser, and desktop apps." + }, + "emptyVaultNudgeTitle": { + "message": "Import existing passwords" + }, + "emptyVaultNudgeBody": { + "message": "Use the importer to quickly transfer logins to Bitwarden without manually adding them." + }, + "emptyVaultNudgeButton": { + "message": "Import now" + }, + "hasItemsVaultNudgeTitle": { + "message": "Welcome to your vault!" + }, + "hasItemsVaultNudgeBody": { + "message": "Autofill items for the current page\nFavorite items for easy access\nSearch your vault for something else" + }, + "newLoginNudgeTitle": { + "message": "Save time with autofill" + }, + "newLoginNudgeBody": { + "message": "Include a Website so this login appears as an autofill suggestion." + }, + "newCardNudgeTitle": { + "message": "Seamless online checkout" + }, + "newCardNudgeBody": { + "message": "With cards, easily autofill payment forms securely and accurately." + }, + "newIdentityNudgeTitle": { + "message": "Simplify creating accounts" + }, + "newIdentityNudgeBody": { + "message": "With identities, quickly autofill long registration or contact forms." + }, + "newNoteNudgeTitle": { + "message": "Keep your sensitive data safe" + }, + "newNoteNudgeBody": { + "message": "With notes, securely store sensitive data like banking or insurance details." + }, + "newSshNudgeTitle": { + "message": "Developer-friendly SSH access" + }, + "newSshNudgeBody": { + "message": "Store your keys and connect with the SSH agent for fast, encrypted authentication." } } diff --git a/apps/browser/src/_locales/nb/messages.json b/apps/browser/src/_locales/nb/messages.json index 7fa41eed68b..f3d87aaa067 100644 --- a/apps/browser/src/_locales/nb/messages.json +++ b/apps/browser/src/_locales/nb/messages.json @@ -2,6 +2,9 @@ "appName": { "message": "Bitwarden" }, + "appLogoLabel": { + "message": "Bitwarden logo" + }, "extName": { "message": "Bitwarden passordbehandler", "description": "Extension name, MUST be less than 40 characters (Safari restriction)" @@ -883,6 +886,9 @@ "followTheStepsBelowToFinishLoggingIn": { "message": "Follow the steps below to finish logging in." }, + "followTheStepsBelowToFinishLoggingInWithSecurityKey": { + "message": "Follow the steps below to finish logging in with your security key." + }, "restartRegistration": { "message": "Start registreringen på nytt" }, @@ -1056,23 +1062,49 @@ "notificationAddSave": { "message": "Ja, lagre nå" }, - "loginSaveSuccessDetails": { - "message": "$USERNAME$ saved to Bitwarden.", + "notificationViewAria": { + "message": "View $ITEMNAME$, opens in new window", "placeholders": { - "username": { + "itemName": { "content": "$1" } }, - "description": "Shown to user after login is saved." + "description": "Aria label for the view button in notification bar confirmation message" }, - "loginUpdatedSuccessDetails": { - "message": "$USERNAME$ updated in Bitwarden.", + "notificationEditTooltip": { + "message": "Edit before saving", + "description": "Tooltip and Aria label for edit button on cipher item" + }, + "newNotification": { + "message": "New notification" + }, + "labelWithNotification": { + "message": "$LABEL$: New notification", + "description": "Label for the notification with a new login suggestion.", "placeholders": { - "username": { + "label": { + "content": "$1", + "example": "Login" + } + } + }, + "loginSaveConfirmation": { + "message": "$ITEMNAME$ saved to Bitwarden.", + "placeholders": { + "itemName": { "content": "$1" } }, - "description": "Shown to user after login is updated." + "description": "Shown to user after item is saved." + }, + "loginUpdatedConfirmation": { + "message": "$ITEMNAME$ updated in Bitwarden.", + "placeholders": { + "itemName": { + "content": "$1" + } + }, + "description": "Shown to user after item is updated." }, "saveAsNewLoginAction": { "message": "Save as new login", @@ -1082,12 +1114,12 @@ "message": "Update login", "description": "Button text for updating an existing login entry." }, - "saveLoginPrompt": { - "message": "Save login?", + "saveLogin": { + "message": "Save login", "description": "Prompt asking the user if they want to save their login details." }, - "updateLoginPrompt": { - "message": "Update existing login?", + "updateLogin": { + "message": "Update existing login", "description": "Prompt asking the user if they want to update an existing login entry." }, "loginSaveSuccess": { @@ -1153,7 +1185,7 @@ "message": "Ja, oppdater nå" }, "notificationUnlockDesc": { - "message": "Unlock your Bitwarden vault to complete the autofill request." + "message": "Lås opp Bitwarden-hvelvet ditt for å utføre auto-utfyllingen." }, "notificationUnlock": { "message": "Lås opp" @@ -4928,8 +4960,8 @@ "message": "Passord ble generert på nytt", "description": "Notification message for when a password has been regenerated" }, - "saveLoginToBitwarden": { - "message": "Vil du lagre påloggingen i Bitwarden?", + "saveToBitwarden": { + "message": "Save to Bitwarden", "description": "Confirmation message for saving a login to Bitwarden" }, "spaceCharacterDescriptor": { @@ -5169,6 +5201,12 @@ "changeAtRiskPassword": { "message": "Change at-risk password" }, + "settingsVaultOptions": { + "message": "Vault options" + }, + "emptyVaultDescription": { + "message": "The vault protects more than just your passwords. Store secure logins, IDs, cards and notes securely here." + }, "introCarouselLabel": { "message": "Welcome to Bitwarden" }, @@ -5195,5 +5233,50 @@ }, "secureDevicesBody": { "message": "Save unlimited passwords across unlimited devices with Bitwarden mobile, browser, and desktop apps." + }, + "emptyVaultNudgeTitle": { + "message": "Import existing passwords" + }, + "emptyVaultNudgeBody": { + "message": "Use the importer to quickly transfer logins to Bitwarden without manually adding them." + }, + "emptyVaultNudgeButton": { + "message": "Import now" + }, + "hasItemsVaultNudgeTitle": { + "message": "Welcome to your vault!" + }, + "hasItemsVaultNudgeBody": { + "message": "Autofill items for the current page\nFavorite items for easy access\nSearch your vault for something else" + }, + "newLoginNudgeTitle": { + "message": "Save time with autofill" + }, + "newLoginNudgeBody": { + "message": "Include a Website so this login appears as an autofill suggestion." + }, + "newCardNudgeTitle": { + "message": "Seamless online checkout" + }, + "newCardNudgeBody": { + "message": "With cards, easily autofill payment forms securely and accurately." + }, + "newIdentityNudgeTitle": { + "message": "Simplify creating accounts" + }, + "newIdentityNudgeBody": { + "message": "With identities, quickly autofill long registration or contact forms." + }, + "newNoteNudgeTitle": { + "message": "Keep your sensitive data safe" + }, + "newNoteNudgeBody": { + "message": "With notes, securely store sensitive data like banking or insurance details." + }, + "newSshNudgeTitle": { + "message": "Developer-friendly SSH access" + }, + "newSshNudgeBody": { + "message": "Store your keys and connect with the SSH agent for fast, encrypted authentication." } } diff --git a/apps/browser/src/_locales/ne/messages.json b/apps/browser/src/_locales/ne/messages.json index 7bec167e1c6..99a09c75d41 100644 --- a/apps/browser/src/_locales/ne/messages.json +++ b/apps/browser/src/_locales/ne/messages.json @@ -2,6 +2,9 @@ "appName": { "message": "Bitwarden" }, + "appLogoLabel": { + "message": "Bitwarden logo" + }, "extName": { "message": "Bitwarden Password Manager", "description": "Extension name, MUST be less than 40 characters (Safari restriction)" @@ -883,6 +886,9 @@ "followTheStepsBelowToFinishLoggingIn": { "message": "Follow the steps below to finish logging in." }, + "followTheStepsBelowToFinishLoggingInWithSecurityKey": { + "message": "Follow the steps below to finish logging in with your security key." + }, "restartRegistration": { "message": "Restart registration" }, @@ -1056,23 +1062,49 @@ "notificationAddSave": { "message": "Save" }, - "loginSaveSuccessDetails": { - "message": "$USERNAME$ saved to Bitwarden.", + "notificationViewAria": { + "message": "View $ITEMNAME$, opens in new window", "placeholders": { - "username": { + "itemName": { "content": "$1" } }, - "description": "Shown to user after login is saved." + "description": "Aria label for the view button in notification bar confirmation message" }, - "loginUpdatedSuccessDetails": { - "message": "$USERNAME$ updated in Bitwarden.", + "notificationEditTooltip": { + "message": "Edit before saving", + "description": "Tooltip and Aria label for edit button on cipher item" + }, + "newNotification": { + "message": "New notification" + }, + "labelWithNotification": { + "message": "$LABEL$: New notification", + "description": "Label for the notification with a new login suggestion.", "placeholders": { - "username": { + "label": { + "content": "$1", + "example": "Login" + } + } + }, + "loginSaveConfirmation": { + "message": "$ITEMNAME$ saved to Bitwarden.", + "placeholders": { + "itemName": { "content": "$1" } }, - "description": "Shown to user after login is updated." + "description": "Shown to user after item is saved." + }, + "loginUpdatedConfirmation": { + "message": "$ITEMNAME$ updated in Bitwarden.", + "placeholders": { + "itemName": { + "content": "$1" + } + }, + "description": "Shown to user after item is updated." }, "saveAsNewLoginAction": { "message": "Save as new login", @@ -1082,12 +1114,12 @@ "message": "Update login", "description": "Button text for updating an existing login entry." }, - "saveLoginPrompt": { - "message": "Save login?", + "saveLogin": { + "message": "Save login", "description": "Prompt asking the user if they want to save their login details." }, - "updateLoginPrompt": { - "message": "Update existing login?", + "updateLogin": { + "message": "Update existing login", "description": "Prompt asking the user if they want to update an existing login entry." }, "loginSaveSuccess": { @@ -4928,8 +4960,8 @@ "message": "Password regenerated", "description": "Notification message for when a password has been regenerated" }, - "saveLoginToBitwarden": { - "message": "Save login to Bitwarden?", + "saveToBitwarden": { + "message": "Save to Bitwarden", "description": "Confirmation message for saving a login to Bitwarden" }, "spaceCharacterDescriptor": { @@ -5169,6 +5201,12 @@ "changeAtRiskPassword": { "message": "Change at-risk password" }, + "settingsVaultOptions": { + "message": "Vault options" + }, + "emptyVaultDescription": { + "message": "The vault protects more than just your passwords. Store secure logins, IDs, cards and notes securely here." + }, "introCarouselLabel": { "message": "Welcome to Bitwarden" }, @@ -5195,5 +5233,50 @@ }, "secureDevicesBody": { "message": "Save unlimited passwords across unlimited devices with Bitwarden mobile, browser, and desktop apps." + }, + "emptyVaultNudgeTitle": { + "message": "Import existing passwords" + }, + "emptyVaultNudgeBody": { + "message": "Use the importer to quickly transfer logins to Bitwarden without manually adding them." + }, + "emptyVaultNudgeButton": { + "message": "Import now" + }, + "hasItemsVaultNudgeTitle": { + "message": "Welcome to your vault!" + }, + "hasItemsVaultNudgeBody": { + "message": "Autofill items for the current page\nFavorite items for easy access\nSearch your vault for something else" + }, + "newLoginNudgeTitle": { + "message": "Save time with autofill" + }, + "newLoginNudgeBody": { + "message": "Include a Website so this login appears as an autofill suggestion." + }, + "newCardNudgeTitle": { + "message": "Seamless online checkout" + }, + "newCardNudgeBody": { + "message": "With cards, easily autofill payment forms securely and accurately." + }, + "newIdentityNudgeTitle": { + "message": "Simplify creating accounts" + }, + "newIdentityNudgeBody": { + "message": "With identities, quickly autofill long registration or contact forms." + }, + "newNoteNudgeTitle": { + "message": "Keep your sensitive data safe" + }, + "newNoteNudgeBody": { + "message": "With notes, securely store sensitive data like banking or insurance details." + }, + "newSshNudgeTitle": { + "message": "Developer-friendly SSH access" + }, + "newSshNudgeBody": { + "message": "Store your keys and connect with the SSH agent for fast, encrypted authentication." } } diff --git a/apps/browser/src/_locales/nl/messages.json b/apps/browser/src/_locales/nl/messages.json index dd101e62e75..2934c5da3fd 100644 --- a/apps/browser/src/_locales/nl/messages.json +++ b/apps/browser/src/_locales/nl/messages.json @@ -2,6 +2,9 @@ "appName": { "message": "Bitwarden" }, + "appLogoLabel": { + "message": "Bitwarden-logo" + }, "extName": { "message": "Bitwarden - wachtwoordbeheerder", "description": "Extension name, MUST be less than 40 characters (Safari restriction)" @@ -883,6 +886,9 @@ "followTheStepsBelowToFinishLoggingIn": { "message": "Volg de onderstaande stappen om het inloggen af te ronden." }, + "followTheStepsBelowToFinishLoggingInWithSecurityKey": { + "message": "Volg onderstaande stappen om in te loggen met je beveiligingssleutel." + }, "restartRegistration": { "message": "Registratie herstarten" }, @@ -1056,23 +1062,49 @@ "notificationAddSave": { "message": "Ja, nu opslaan" }, - "loginSaveSuccessDetails": { - "message": "$USERNAME$ opgeslagen in Bitwarden.", + "notificationViewAria": { + "message": "$ITEMNAME$ bekijken, opent in nieuw venster", "placeholders": { - "username": { + "itemName": { "content": "$1" } }, - "description": "Shown to user after login is saved." + "description": "Aria label for the view button in notification bar confirmation message" }, - "loginUpdatedSuccessDetails": { - "message": "$USERNAME$ bijgewerkt in Bitwarden.", + "notificationEditTooltip": { + "message": "Bewerken voor opslaan", + "description": "Tooltip and Aria label for edit button on cipher item" + }, + "newNotification": { + "message": "Nieuwe melding" + }, + "labelWithNotification": { + "message": "$LABEL$: Nieuwe melding", + "description": "Label for the notification with a new login suggestion.", "placeholders": { - "username": { + "label": { + "content": "$1", + "example": "Login" + } + } + }, + "loginSaveConfirmation": { + "message": "$ITEMNAME$ opgeslagen in Bitwarden.", + "placeholders": { + "itemName": { "content": "$1" } }, - "description": "Shown to user after login is updated." + "description": "Shown to user after item is saved." + }, + "loginUpdatedConfirmation": { + "message": "$ITEMNAME$ bijgewerkt in Bitwarden.", + "placeholders": { + "itemName": { + "content": "$1" + } + }, + "description": "Shown to user after item is updated." }, "saveAsNewLoginAction": { "message": "Opslaan als nieuwe login", @@ -1082,12 +1114,12 @@ "message": "Login bijwerken", "description": "Button text for updating an existing login entry." }, - "saveLoginPrompt": { - "message": "Login opslaan?", + "saveLogin": { + "message": "Login opslaan", "description": "Prompt asking the user if they want to save their login details." }, - "updateLoginPrompt": { - "message": "Bestaande login bijwerken?", + "updateLogin": { + "message": "Bestaande login bijwerken", "description": "Prompt asking the user if they want to update an existing login entry." }, "loginSaveSuccess": { @@ -4928,8 +4960,8 @@ "message": "Wachtwoord opnieuw gegenereerd", "description": "Notification message for when a password has been regenerated" }, - "saveLoginToBitwarden": { - "message": "Login opslaan in Bitwarden?", + "saveToBitwarden": { + "message": "Opslaan in Bitwarden", "description": "Confirmation message for saving a login to Bitwarden" }, "spaceCharacterDescriptor": { @@ -5169,6 +5201,12 @@ "changeAtRiskPassword": { "message": "Risicovol wachtwoord wijzigen" }, + "settingsVaultOptions": { + "message": "Kluis-instellingen" + }, + "emptyVaultDescription": { + "message": "De kluis beschermt meer dan alleen je wachtwoorden. Sla hier beveiligde inloggegevens, ID's, kaarten en notities op." + }, "introCarouselLabel": { "message": "Welkom bij Bitwarden" }, @@ -5195,5 +5233,50 @@ }, "secureDevicesBody": { "message": "Onbeperkt wachtwoorden opslaan op alle apparaten met Bitwarden-apps voor mobiel, browser en desktop." + }, + "emptyVaultNudgeTitle": { + "message": "Bestaande wachtwoorden importeren" + }, + "emptyVaultNudgeBody": { + "message": "Gebruik de importer om snel logins naar Bitwarden over te dragen zonder ze handmatig toe te voegen." + }, + "emptyVaultNudgeButton": { + "message": "Nu importeren" + }, + "hasItemsVaultNudgeTitle": { + "message": "Welkom in je kluis!" + }, + "hasItemsVaultNudgeBody": { + "message": "Autofill items voor de huidige pagina\nFavoriete items voor eenvoudige toegang\nDoorzoek je kluis naar iets anders" + }, + "newLoginNudgeTitle": { + "message": "Tijd besparen met automatisch aanvullen" + }, + "newLoginNudgeBody": { + "message": "Voeg een website toe zodat deze login wordt weergegeven als een automatische invulsuggestie." + }, + "newCardNudgeTitle": { + "message": "Naadloos online afrekenen" + }, + "newCardNudgeBody": { + "message": "Met kaarten gemakkelijk, veilig en accuraat automatisch invullen van betaalformulieren." + }, + "newIdentityNudgeTitle": { + "message": "Vereenvoudig het aanmaken van accounts" + }, + "newIdentityNudgeBody": { + "message": "Met identiteiten vul je lange registratie- of contactformulieren snel automatisch in." + }, + "newNoteNudgeTitle": { + "message": "Houd je gevoelige gegevens veilig" + }, + "newNoteNudgeBody": { + "message": "Met notities veilig opslaan van gevoelige gegevens zoals bank- of verzekeringsgegevens." + }, + "newSshNudgeTitle": { + "message": "Ontwikkelaars-vriendelijke SSH-toegang" + }, + "newSshNudgeBody": { + "message": "Sla je sleutels op en verbind met de SSH-agent voor snelle, versleutelde authenticatie." } } diff --git a/apps/browser/src/_locales/nn/messages.json b/apps/browser/src/_locales/nn/messages.json index 7bec167e1c6..99a09c75d41 100644 --- a/apps/browser/src/_locales/nn/messages.json +++ b/apps/browser/src/_locales/nn/messages.json @@ -2,6 +2,9 @@ "appName": { "message": "Bitwarden" }, + "appLogoLabel": { + "message": "Bitwarden logo" + }, "extName": { "message": "Bitwarden Password Manager", "description": "Extension name, MUST be less than 40 characters (Safari restriction)" @@ -883,6 +886,9 @@ "followTheStepsBelowToFinishLoggingIn": { "message": "Follow the steps below to finish logging in." }, + "followTheStepsBelowToFinishLoggingInWithSecurityKey": { + "message": "Follow the steps below to finish logging in with your security key." + }, "restartRegistration": { "message": "Restart registration" }, @@ -1056,23 +1062,49 @@ "notificationAddSave": { "message": "Save" }, - "loginSaveSuccessDetails": { - "message": "$USERNAME$ saved to Bitwarden.", + "notificationViewAria": { + "message": "View $ITEMNAME$, opens in new window", "placeholders": { - "username": { + "itemName": { "content": "$1" } }, - "description": "Shown to user after login is saved." + "description": "Aria label for the view button in notification bar confirmation message" }, - "loginUpdatedSuccessDetails": { - "message": "$USERNAME$ updated in Bitwarden.", + "notificationEditTooltip": { + "message": "Edit before saving", + "description": "Tooltip and Aria label for edit button on cipher item" + }, + "newNotification": { + "message": "New notification" + }, + "labelWithNotification": { + "message": "$LABEL$: New notification", + "description": "Label for the notification with a new login suggestion.", "placeholders": { - "username": { + "label": { + "content": "$1", + "example": "Login" + } + } + }, + "loginSaveConfirmation": { + "message": "$ITEMNAME$ saved to Bitwarden.", + "placeholders": { + "itemName": { "content": "$1" } }, - "description": "Shown to user after login is updated." + "description": "Shown to user after item is saved." + }, + "loginUpdatedConfirmation": { + "message": "$ITEMNAME$ updated in Bitwarden.", + "placeholders": { + "itemName": { + "content": "$1" + } + }, + "description": "Shown to user after item is updated." }, "saveAsNewLoginAction": { "message": "Save as new login", @@ -1082,12 +1114,12 @@ "message": "Update login", "description": "Button text for updating an existing login entry." }, - "saveLoginPrompt": { - "message": "Save login?", + "saveLogin": { + "message": "Save login", "description": "Prompt asking the user if they want to save their login details." }, - "updateLoginPrompt": { - "message": "Update existing login?", + "updateLogin": { + "message": "Update existing login", "description": "Prompt asking the user if they want to update an existing login entry." }, "loginSaveSuccess": { @@ -4928,8 +4960,8 @@ "message": "Password regenerated", "description": "Notification message for when a password has been regenerated" }, - "saveLoginToBitwarden": { - "message": "Save login to Bitwarden?", + "saveToBitwarden": { + "message": "Save to Bitwarden", "description": "Confirmation message for saving a login to Bitwarden" }, "spaceCharacterDescriptor": { @@ -5169,6 +5201,12 @@ "changeAtRiskPassword": { "message": "Change at-risk password" }, + "settingsVaultOptions": { + "message": "Vault options" + }, + "emptyVaultDescription": { + "message": "The vault protects more than just your passwords. Store secure logins, IDs, cards and notes securely here." + }, "introCarouselLabel": { "message": "Welcome to Bitwarden" }, @@ -5195,5 +5233,50 @@ }, "secureDevicesBody": { "message": "Save unlimited passwords across unlimited devices with Bitwarden mobile, browser, and desktop apps." + }, + "emptyVaultNudgeTitle": { + "message": "Import existing passwords" + }, + "emptyVaultNudgeBody": { + "message": "Use the importer to quickly transfer logins to Bitwarden without manually adding them." + }, + "emptyVaultNudgeButton": { + "message": "Import now" + }, + "hasItemsVaultNudgeTitle": { + "message": "Welcome to your vault!" + }, + "hasItemsVaultNudgeBody": { + "message": "Autofill items for the current page\nFavorite items for easy access\nSearch your vault for something else" + }, + "newLoginNudgeTitle": { + "message": "Save time with autofill" + }, + "newLoginNudgeBody": { + "message": "Include a Website so this login appears as an autofill suggestion." + }, + "newCardNudgeTitle": { + "message": "Seamless online checkout" + }, + "newCardNudgeBody": { + "message": "With cards, easily autofill payment forms securely and accurately." + }, + "newIdentityNudgeTitle": { + "message": "Simplify creating accounts" + }, + "newIdentityNudgeBody": { + "message": "With identities, quickly autofill long registration or contact forms." + }, + "newNoteNudgeTitle": { + "message": "Keep your sensitive data safe" + }, + "newNoteNudgeBody": { + "message": "With notes, securely store sensitive data like banking or insurance details." + }, + "newSshNudgeTitle": { + "message": "Developer-friendly SSH access" + }, + "newSshNudgeBody": { + "message": "Store your keys and connect with the SSH agent for fast, encrypted authentication." } } diff --git a/apps/browser/src/_locales/or/messages.json b/apps/browser/src/_locales/or/messages.json index 7bec167e1c6..99a09c75d41 100644 --- a/apps/browser/src/_locales/or/messages.json +++ b/apps/browser/src/_locales/or/messages.json @@ -2,6 +2,9 @@ "appName": { "message": "Bitwarden" }, + "appLogoLabel": { + "message": "Bitwarden logo" + }, "extName": { "message": "Bitwarden Password Manager", "description": "Extension name, MUST be less than 40 characters (Safari restriction)" @@ -883,6 +886,9 @@ "followTheStepsBelowToFinishLoggingIn": { "message": "Follow the steps below to finish logging in." }, + "followTheStepsBelowToFinishLoggingInWithSecurityKey": { + "message": "Follow the steps below to finish logging in with your security key." + }, "restartRegistration": { "message": "Restart registration" }, @@ -1056,23 +1062,49 @@ "notificationAddSave": { "message": "Save" }, - "loginSaveSuccessDetails": { - "message": "$USERNAME$ saved to Bitwarden.", + "notificationViewAria": { + "message": "View $ITEMNAME$, opens in new window", "placeholders": { - "username": { + "itemName": { "content": "$1" } }, - "description": "Shown to user after login is saved." + "description": "Aria label for the view button in notification bar confirmation message" }, - "loginUpdatedSuccessDetails": { - "message": "$USERNAME$ updated in Bitwarden.", + "notificationEditTooltip": { + "message": "Edit before saving", + "description": "Tooltip and Aria label for edit button on cipher item" + }, + "newNotification": { + "message": "New notification" + }, + "labelWithNotification": { + "message": "$LABEL$: New notification", + "description": "Label for the notification with a new login suggestion.", "placeholders": { - "username": { + "label": { + "content": "$1", + "example": "Login" + } + } + }, + "loginSaveConfirmation": { + "message": "$ITEMNAME$ saved to Bitwarden.", + "placeholders": { + "itemName": { "content": "$1" } }, - "description": "Shown to user after login is updated." + "description": "Shown to user after item is saved." + }, + "loginUpdatedConfirmation": { + "message": "$ITEMNAME$ updated in Bitwarden.", + "placeholders": { + "itemName": { + "content": "$1" + } + }, + "description": "Shown to user after item is updated." }, "saveAsNewLoginAction": { "message": "Save as new login", @@ -1082,12 +1114,12 @@ "message": "Update login", "description": "Button text for updating an existing login entry." }, - "saveLoginPrompt": { - "message": "Save login?", + "saveLogin": { + "message": "Save login", "description": "Prompt asking the user if they want to save their login details." }, - "updateLoginPrompt": { - "message": "Update existing login?", + "updateLogin": { + "message": "Update existing login", "description": "Prompt asking the user if they want to update an existing login entry." }, "loginSaveSuccess": { @@ -4928,8 +4960,8 @@ "message": "Password regenerated", "description": "Notification message for when a password has been regenerated" }, - "saveLoginToBitwarden": { - "message": "Save login to Bitwarden?", + "saveToBitwarden": { + "message": "Save to Bitwarden", "description": "Confirmation message for saving a login to Bitwarden" }, "spaceCharacterDescriptor": { @@ -5169,6 +5201,12 @@ "changeAtRiskPassword": { "message": "Change at-risk password" }, + "settingsVaultOptions": { + "message": "Vault options" + }, + "emptyVaultDescription": { + "message": "The vault protects more than just your passwords. Store secure logins, IDs, cards and notes securely here." + }, "introCarouselLabel": { "message": "Welcome to Bitwarden" }, @@ -5195,5 +5233,50 @@ }, "secureDevicesBody": { "message": "Save unlimited passwords across unlimited devices with Bitwarden mobile, browser, and desktop apps." + }, + "emptyVaultNudgeTitle": { + "message": "Import existing passwords" + }, + "emptyVaultNudgeBody": { + "message": "Use the importer to quickly transfer logins to Bitwarden without manually adding them." + }, + "emptyVaultNudgeButton": { + "message": "Import now" + }, + "hasItemsVaultNudgeTitle": { + "message": "Welcome to your vault!" + }, + "hasItemsVaultNudgeBody": { + "message": "Autofill items for the current page\nFavorite items for easy access\nSearch your vault for something else" + }, + "newLoginNudgeTitle": { + "message": "Save time with autofill" + }, + "newLoginNudgeBody": { + "message": "Include a Website so this login appears as an autofill suggestion." + }, + "newCardNudgeTitle": { + "message": "Seamless online checkout" + }, + "newCardNudgeBody": { + "message": "With cards, easily autofill payment forms securely and accurately." + }, + "newIdentityNudgeTitle": { + "message": "Simplify creating accounts" + }, + "newIdentityNudgeBody": { + "message": "With identities, quickly autofill long registration or contact forms." + }, + "newNoteNudgeTitle": { + "message": "Keep your sensitive data safe" + }, + "newNoteNudgeBody": { + "message": "With notes, securely store sensitive data like banking or insurance details." + }, + "newSshNudgeTitle": { + "message": "Developer-friendly SSH access" + }, + "newSshNudgeBody": { + "message": "Store your keys and connect with the SSH agent for fast, encrypted authentication." } } diff --git a/apps/browser/src/_locales/pl/messages.json b/apps/browser/src/_locales/pl/messages.json index 3d25e6f123c..042cafb0300 100644 --- a/apps/browser/src/_locales/pl/messages.json +++ b/apps/browser/src/_locales/pl/messages.json @@ -2,6 +2,9 @@ "appName": { "message": "Bitwarden" }, + "appLogoLabel": { + "message": "Bitwarden logo" + }, "extName": { "message": "Menedżer Haseł Bitwarden", "description": "Extension name, MUST be less than 40 characters (Safari restriction)" @@ -883,6 +886,9 @@ "followTheStepsBelowToFinishLoggingIn": { "message": "Wykonaj poniższe kroki, by dokończyć logowanie" }, + "followTheStepsBelowToFinishLoggingInWithSecurityKey": { + "message": "Wykonaj poniższe kroki, aby zakończyć logowanie za pomocą klucza bezpieczeństwa." + }, "restartRegistration": { "message": "Zrestartuj rejestrację" }, @@ -1056,23 +1062,49 @@ "notificationAddSave": { "message": "Zapisz" }, - "loginSaveSuccessDetails": { - "message": "$USERNAME$ został zapisany w Bitwarden.", + "notificationViewAria": { + "message": "Wyświetl $ITEMNAME$, otworzy się w nowym oknie", "placeholders": { - "username": { + "itemName": { "content": "$1" } }, - "description": "Shown to user after login is saved." + "description": "Aria label for the view button in notification bar confirmation message" }, - "loginUpdatedSuccessDetails": { - "message": "$USERNAME$ został zaktualizowany w Bitwarden.", + "notificationEditTooltip": { + "message": "Edytuj przed zapisaniem", + "description": "Tooltip and Aria label for edit button on cipher item" + }, + "newNotification": { + "message": "Nowe powiadomienie" + }, + "labelWithNotification": { + "message": "$LABEL$: Nowe powiadomienie", + "description": "Label for the notification with a new login suggestion.", "placeholders": { - "username": { + "label": { + "content": "$1", + "example": "Login" + } + } + }, + "loginSaveConfirmation": { + "message": "$ITEMNAME$ został zapisany w Bitwarden.", + "placeholders": { + "itemName": { "content": "$1" } }, - "description": "Shown to user after login is updated." + "description": "Shown to user after item is saved." + }, + "loginUpdatedConfirmation": { + "message": "$ITEMNAME$ został zaktualizowany w Bitwarden.", + "placeholders": { + "itemName": { + "content": "$1" + } + }, + "description": "Shown to user after item is updated." }, "saveAsNewLoginAction": { "message": "Zapisz jako nowy login", @@ -1082,11 +1114,11 @@ "message": "Zaktualizuj dane logowania", "description": "Button text for updating an existing login entry." }, - "saveLoginPrompt": { - "message": "Zapisać dane logowania?", + "saveLogin": { + "message": "Zapisz dane logowania", "description": "Prompt asking the user if they want to save their login details." }, - "updateLoginPrompt": { + "updateLogin": { "message": "Zaktualizować istniejące dane logowania?", "description": "Prompt asking the user if they want to update an existing login entry." }, @@ -4928,8 +4960,8 @@ "message": "Hasło zostało ponownie wygenerowane", "description": "Notification message for when a password has been regenerated" }, - "saveLoginToBitwarden": { - "message": "Zapisać dane logowania w Bitwarden?", + "saveToBitwarden": { + "message": "Zapisz w Bitwarden", "description": "Confirmation message for saving a login to Bitwarden" }, "spaceCharacterDescriptor": { @@ -5169,6 +5201,12 @@ "changeAtRiskPassword": { "message": "Zmień hasło zagrożone" }, + "settingsVaultOptions": { + "message": "Ustawienia Sejfu" + }, + "emptyVaultDescription": { + "message": "Sejf chroni nie tylko Twoje hasła. Przechowuj tutaj bezpiecznie loginy, identyfikatory, karty i notatki." + }, "introCarouselLabel": { "message": "Witaj w Bitwarden" }, @@ -5195,5 +5233,50 @@ }, "secureDevicesBody": { "message": "Zapisuj nieograniczoną liczbę haseł na nieograniczonej liczbie urządzeń dzięki aplikacjom Bitwarden na urządzenia mobilne, przeglądarki i komputery stacjonarne." + }, + "emptyVaultNudgeTitle": { + "message": "Importuj istniejące hasła" + }, + "emptyVaultNudgeBody": { + "message": "Użyj importera, aby szybko przenieść loginy do Bitwarden bez ręcznego dodawania ich." + }, + "emptyVaultNudgeButton": { + "message": "Importuj teraz" + }, + "hasItemsVaultNudgeTitle": { + "message": "Witaj w Twoim Sejfie!" + }, + "hasItemsVaultNudgeBody": { + "message": "Autouzupełnianie elementów dla bieżącej strony\nUlubione elementy dla łatwego dostępu\nPrzeszukaj swój sejf w poszukiwaniu czegoś innego" + }, + "newLoginNudgeTitle": { + "message": "Oszczędzaj czas dzięki autouzupełnianiu" + }, + "newLoginNudgeBody": { + "message": "Dołącz stronę internetową, aby ten login pojawił się jako sugestia autouzupełniania." + }, + "newCardNudgeTitle": { + "message": "Bezproblemowe zamówienia online" + }, + "newCardNudgeBody": { + "message": "Z kartami łatwe autouzupełnianie formularzy płatności w sposób bezpieczny i dokładny." + }, + "newIdentityNudgeTitle": { + "message": "Uprość tworzenie kont" + }, + "newIdentityNudgeBody": { + "message": "Z tożsamościami, szybko autouzupełnij długie formularze rejestracyjne lub kontaktowe." + }, + "newNoteNudgeTitle": { + "message": "Zachowaj bezpieczeństwo wrażliwych danych" + }, + "newNoteNudgeBody": { + "message": "Z notatkami bezpiecznie przechowuj dane szczególnie chronione, takie jak dane bankowe lub ubezpieczeniowe." + }, + "newSshNudgeTitle": { + "message": "Przyjazny dla deweloperów dostęp SSH" + }, + "newSshNudgeBody": { + "message": "Przechowuj swoje klucze i połącz się z agentem SSH dla szybkiego, szyfrowanego uwierzytelniania." } } diff --git a/apps/browser/src/_locales/pt_BR/messages.json b/apps/browser/src/_locales/pt_BR/messages.json index 9d1744f5473..c4fb561b5be 100644 --- a/apps/browser/src/_locales/pt_BR/messages.json +++ b/apps/browser/src/_locales/pt_BR/messages.json @@ -2,6 +2,9 @@ "appName": { "message": "Bitwarden" }, + "appLogoLabel": { + "message": "Bitwarden logo" + }, "extName": { "message": "Gerenciador de senhas Bitwarden", "description": "Extension name, MUST be less than 40 characters (Safari restriction)" @@ -883,6 +886,9 @@ "followTheStepsBelowToFinishLoggingIn": { "message": "Siga os passos abaixo para finalizar o login." }, + "followTheStepsBelowToFinishLoggingInWithSecurityKey": { + "message": "Follow the steps below to finish logging in with your security key." + }, "restartRegistration": { "message": "Reiniciar registro" }, @@ -1056,23 +1062,49 @@ "notificationAddSave": { "message": "Salvar" }, - "loginSaveSuccessDetails": { - "message": "$USERNAME$ salvo no Bitwarden.", + "notificationViewAria": { + "message": "View $ITEMNAME$, opens in new window", "placeholders": { - "username": { + "itemName": { "content": "$1" } }, - "description": "Shown to user after login is saved." + "description": "Aria label for the view button in notification bar confirmation message" }, - "loginUpdatedSuccessDetails": { - "message": "$USERNAME$ atualizado no Bitwarden.", + "notificationEditTooltip": { + "message": "Edit before saving", + "description": "Tooltip and Aria label for edit button on cipher item" + }, + "newNotification": { + "message": "New notification" + }, + "labelWithNotification": { + "message": "$LABEL$: New notification", + "description": "Label for the notification with a new login suggestion.", "placeholders": { - "username": { + "label": { + "content": "$1", + "example": "Login" + } + } + }, + "loginSaveConfirmation": { + "message": "$ITEMNAME$ saved to Bitwarden.", + "placeholders": { + "itemName": { "content": "$1" } }, - "description": "Shown to user after login is updated." + "description": "Shown to user after item is saved." + }, + "loginUpdatedConfirmation": { + "message": "$ITEMNAME$ updated in Bitwarden.", + "placeholders": { + "itemName": { + "content": "$1" + } + }, + "description": "Shown to user after item is updated." }, "saveAsNewLoginAction": { "message": "Salvar como nova sessão", @@ -1082,12 +1114,12 @@ "message": "Atualizar sessão", "description": "Button text for updating an existing login entry." }, - "saveLoginPrompt": { - "message": "Salvar sessão?", + "saveLogin": { + "message": "Save login", "description": "Prompt asking the user if they want to save their login details." }, - "updateLoginPrompt": { - "message": "Atualizar a sessão atual?", + "updateLogin": { + "message": "Update existing login", "description": "Prompt asking the user if they want to update an existing login entry." }, "loginSaveSuccess": { @@ -4928,8 +4960,8 @@ "message": "Senha regenerada", "description": "Notification message for when a password has been regenerated" }, - "saveLoginToBitwarden": { - "message": "Salvar login no Bitwarden?", + "saveToBitwarden": { + "message": "Save to Bitwarden", "description": "Confirmation message for saving a login to Bitwarden" }, "spaceCharacterDescriptor": { @@ -5169,6 +5201,12 @@ "changeAtRiskPassword": { "message": "Alterar senhas vulneráveis" }, + "settingsVaultOptions": { + "message": "Vault options" + }, + "emptyVaultDescription": { + "message": "The vault protects more than just your passwords. Store secure logins, IDs, cards and notes securely here." + }, "introCarouselLabel": { "message": "Bem-vindo(a) ao Bitwarden" }, @@ -5195,5 +5233,50 @@ }, "secureDevicesBody": { "message": "Guarde quantas senhas quiser e acesse de qualquer lugar com o Bitwarden. No seu celular, navegador e computador." + }, + "emptyVaultNudgeTitle": { + "message": "Import existing passwords" + }, + "emptyVaultNudgeBody": { + "message": "Use the importer to quickly transfer logins to Bitwarden without manually adding them." + }, + "emptyVaultNudgeButton": { + "message": "Import now" + }, + "hasItemsVaultNudgeTitle": { + "message": "Welcome to your vault!" + }, + "hasItemsVaultNudgeBody": { + "message": "Autofill items for the current page\nFavorite items for easy access\nSearch your vault for something else" + }, + "newLoginNudgeTitle": { + "message": "Save time with autofill" + }, + "newLoginNudgeBody": { + "message": "Include a Website so this login appears as an autofill suggestion." + }, + "newCardNudgeTitle": { + "message": "Seamless online checkout" + }, + "newCardNudgeBody": { + "message": "With cards, easily autofill payment forms securely and accurately." + }, + "newIdentityNudgeTitle": { + "message": "Simplify creating accounts" + }, + "newIdentityNudgeBody": { + "message": "With identities, quickly autofill long registration or contact forms." + }, + "newNoteNudgeTitle": { + "message": "Keep your sensitive data safe" + }, + "newNoteNudgeBody": { + "message": "With notes, securely store sensitive data like banking or insurance details." + }, + "newSshNudgeTitle": { + "message": "Developer-friendly SSH access" + }, + "newSshNudgeBody": { + "message": "Store your keys and connect with the SSH agent for fast, encrypted authentication." } } diff --git a/apps/browser/src/_locales/pt_PT/messages.json b/apps/browser/src/_locales/pt_PT/messages.json index daab7fb05d9..0af5cb2d13f 100644 --- a/apps/browser/src/_locales/pt_PT/messages.json +++ b/apps/browser/src/_locales/pt_PT/messages.json @@ -2,6 +2,9 @@ "appName": { "message": "Bitwarden" }, + "appLogoLabel": { + "message": "Logótipo do Bitwarden" + }, "extName": { "message": "Bitwarden - Gestor de Palavras-passe", "description": "Extension name, MUST be less than 40 characters (Safari restriction)" @@ -883,6 +886,9 @@ "followTheStepsBelowToFinishLoggingIn": { "message": "Siga os passos abaixo para concluir o início de sessão." }, + "followTheStepsBelowToFinishLoggingInWithSecurityKey": { + "message": "Siga os passos abaixo para concluir o início de sessão com a sua chave de segurança." + }, "restartRegistration": { "message": "Reiniciar registo" }, @@ -1056,23 +1062,49 @@ "notificationAddSave": { "message": "Guardar" }, - "loginSaveSuccessDetails": { - "message": "$USERNAME$ guardado no Bitwarden.", + "notificationViewAria": { + "message": "Ver $ITEMNAME$, abrir numa nova janela", "placeholders": { - "username": { + "itemName": { "content": "$1" } }, - "description": "Shown to user after login is saved." + "description": "Aria label for the view button in notification bar confirmation message" }, - "loginUpdatedSuccessDetails": { - "message": "$USERNAME$ atualizado no Bitwarden.", + "notificationEditTooltip": { + "message": "Editar antes de guardar", + "description": "Tooltip and Aria label for edit button on cipher item" + }, + "newNotification": { + "message": "Nova notificação" + }, + "labelWithNotification": { + "message": "$LABEL$: Nova notificação", + "description": "Label for the notification with a new login suggestion.", "placeholders": { - "username": { + "label": { + "content": "$1", + "example": "Login" + } + } + }, + "loginSaveConfirmation": { + "message": "$ITEMNAME$ guardado no Bitwarden.", + "placeholders": { + "itemName": { "content": "$1" } }, - "description": "Shown to user after login is updated." + "description": "Shown to user after item is saved." + }, + "loginUpdatedConfirmation": { + "message": "$ITEMNAME$ atualizado no Bitwarden.", + "placeholders": { + "itemName": { + "content": "$1" + } + }, + "description": "Shown to user after item is updated." }, "saveAsNewLoginAction": { "message": "Guardar como nova credencial", @@ -1082,12 +1114,12 @@ "message": "Atualizar credencial", "description": "Button text for updating an existing login entry." }, - "saveLoginPrompt": { - "message": "Guardar credencial?", + "saveLogin": { + "message": "Guardar credencial", "description": "Prompt asking the user if they want to save their login details." }, - "updateLoginPrompt": { - "message": "Atualizar credencial existente?", + "updateLogin": { + "message": "Atualizar credencial existente", "description": "Prompt asking the user if they want to update an existing login entry." }, "loginSaveSuccess": { @@ -4928,8 +4960,8 @@ "message": "Palavra-passe gerada novamente", "description": "Notification message for when a password has been regenerated" }, - "saveLoginToBitwarden": { - "message": "Guardar credencial no Bitwarden?", + "saveToBitwarden": { + "message": "Guardar no Bitwarden", "description": "Confirmation message for saving a login to Bitwarden" }, "spaceCharacterDescriptor": { @@ -5169,6 +5201,12 @@ "changeAtRiskPassword": { "message": "Alterar palavra-passe em risco" }, + "settingsVaultOptions": { + "message": "Opções do cofre" + }, + "emptyVaultDescription": { + "message": "O cofre protege mais do que apenas as suas palavras-passe. Guarde aqui credenciais, IDs, cartões e notas de forma segura." + }, "introCarouselLabel": { "message": "Bem-vindo ao Bitwarden" }, @@ -5195,5 +5233,50 @@ }, "secureDevicesBody": { "message": "Guarde palavras-passe ilimitadas em dispositivos ilimitados com as apps Bitwarden para telemóvel, navegador e computador." + }, + "emptyVaultNudgeTitle": { + "message": "Importar palavras-passe existentes" + }, + "emptyVaultNudgeBody": { + "message": "Utilize o importador para transferir rapidamente as credenciais para o Bitwarden sem as adicionar manualmente." + }, + "emptyVaultNudgeButton": { + "message": "Importar agora" + }, + "hasItemsVaultNudgeTitle": { + "message": "Bem-vindo ao seu cofre!" + }, + "hasItemsVaultNudgeBody": { + "message": "Preenchimento automático de itens para a página atual\nItens favoritos para um acesso fácil\nProcurar outra coisa no seu cofre" + }, + "newLoginNudgeTitle": { + "message": "Poupe tempo com o preenchimento automático" + }, + "newLoginNudgeBody": { + "message": "Inclua um site para que esta credencial apareça como uma sugestão de preenchimento automático." + }, + "newCardNudgeTitle": { + "message": "Pagamentos online sem problemas" + }, + "newCardNudgeBody": { + "message": "Com os cartões, preencha facilmente formulários de pagamento de forma segura e exata." + }, + "newIdentityNudgeTitle": { + "message": "Simplifique a criação de contas" + }, + "newIdentityNudgeBody": { + "message": "Com as identidades, preencha rapidamente formulários de registo ou de contacto longos." + }, + "newNoteNudgeTitle": { + "message": "Mantenha os seus dados sensíveis seguros" + }, + "newNoteNudgeBody": { + "message": "Com as notas, armazene de forma segura dados sensíveis, como dados bancários ou de seguros." + }, + "newSshNudgeTitle": { + "message": "Acesso SSH de fácil utilização pelos programadores" + }, + "newSshNudgeBody": { + "message": "Guarde as suas chaves e ligue-se ao agente SSH para uma autenticação rápida e encriptada." } } diff --git a/apps/browser/src/_locales/ro/messages.json b/apps/browser/src/_locales/ro/messages.json index bcdf10f37f1..bf08e0969b2 100644 --- a/apps/browser/src/_locales/ro/messages.json +++ b/apps/browser/src/_locales/ro/messages.json @@ -2,6 +2,9 @@ "appName": { "message": "Bitwarden" }, + "appLogoLabel": { + "message": "Bitwarden logo" + }, "extName": { "message": "Bitwarden - Manager Gratuit de Parole", "description": "Extension name, MUST be less than 40 characters (Safari restriction)" @@ -883,6 +886,9 @@ "followTheStepsBelowToFinishLoggingIn": { "message": "Follow the steps below to finish logging in." }, + "followTheStepsBelowToFinishLoggingInWithSecurityKey": { + "message": "Follow the steps below to finish logging in with your security key." + }, "restartRegistration": { "message": "Reporniți înregistrarea" }, @@ -1056,23 +1062,49 @@ "notificationAddSave": { "message": "Salvare" }, - "loginSaveSuccessDetails": { - "message": "$USERNAME$ saved to Bitwarden.", + "notificationViewAria": { + "message": "View $ITEMNAME$, opens in new window", "placeholders": { - "username": { + "itemName": { "content": "$1" } }, - "description": "Shown to user after login is saved." + "description": "Aria label for the view button in notification bar confirmation message" }, - "loginUpdatedSuccessDetails": { - "message": "$USERNAME$ updated in Bitwarden.", + "notificationEditTooltip": { + "message": "Edit before saving", + "description": "Tooltip and Aria label for edit button on cipher item" + }, + "newNotification": { + "message": "New notification" + }, + "labelWithNotification": { + "message": "$LABEL$: New notification", + "description": "Label for the notification with a new login suggestion.", "placeholders": { - "username": { + "label": { + "content": "$1", + "example": "Login" + } + } + }, + "loginSaveConfirmation": { + "message": "$ITEMNAME$ saved to Bitwarden.", + "placeholders": { + "itemName": { "content": "$1" } }, - "description": "Shown to user after login is updated." + "description": "Shown to user after item is saved." + }, + "loginUpdatedConfirmation": { + "message": "$ITEMNAME$ updated in Bitwarden.", + "placeholders": { + "itemName": { + "content": "$1" + } + }, + "description": "Shown to user after item is updated." }, "saveAsNewLoginAction": { "message": "Save as new login", @@ -1082,12 +1114,12 @@ "message": "Update login", "description": "Button text for updating an existing login entry." }, - "saveLoginPrompt": { - "message": "Save login?", + "saveLogin": { + "message": "Save login", "description": "Prompt asking the user if they want to save their login details." }, - "updateLoginPrompt": { - "message": "Update existing login?", + "updateLogin": { + "message": "Update existing login", "description": "Prompt asking the user if they want to update an existing login entry." }, "loginSaveSuccess": { @@ -4928,8 +4960,8 @@ "message": "Password regenerated", "description": "Notification message for when a password has been regenerated" }, - "saveLoginToBitwarden": { - "message": "Save login to Bitwarden?", + "saveToBitwarden": { + "message": "Save to Bitwarden", "description": "Confirmation message for saving a login to Bitwarden" }, "spaceCharacterDescriptor": { @@ -5169,6 +5201,12 @@ "changeAtRiskPassword": { "message": "Change at-risk password" }, + "settingsVaultOptions": { + "message": "Vault options" + }, + "emptyVaultDescription": { + "message": "The vault protects more than just your passwords. Store secure logins, IDs, cards and notes securely here." + }, "introCarouselLabel": { "message": "Welcome to Bitwarden" }, @@ -5195,5 +5233,50 @@ }, "secureDevicesBody": { "message": "Save unlimited passwords across unlimited devices with Bitwarden mobile, browser, and desktop apps." + }, + "emptyVaultNudgeTitle": { + "message": "Import existing passwords" + }, + "emptyVaultNudgeBody": { + "message": "Use the importer to quickly transfer logins to Bitwarden without manually adding them." + }, + "emptyVaultNudgeButton": { + "message": "Import now" + }, + "hasItemsVaultNudgeTitle": { + "message": "Welcome to your vault!" + }, + "hasItemsVaultNudgeBody": { + "message": "Autofill items for the current page\nFavorite items for easy access\nSearch your vault for something else" + }, + "newLoginNudgeTitle": { + "message": "Save time with autofill" + }, + "newLoginNudgeBody": { + "message": "Include a Website so this login appears as an autofill suggestion." + }, + "newCardNudgeTitle": { + "message": "Seamless online checkout" + }, + "newCardNudgeBody": { + "message": "With cards, easily autofill payment forms securely and accurately." + }, + "newIdentityNudgeTitle": { + "message": "Simplify creating accounts" + }, + "newIdentityNudgeBody": { + "message": "With identities, quickly autofill long registration or contact forms." + }, + "newNoteNudgeTitle": { + "message": "Keep your sensitive data safe" + }, + "newNoteNudgeBody": { + "message": "With notes, securely store sensitive data like banking or insurance details." + }, + "newSshNudgeTitle": { + "message": "Developer-friendly SSH access" + }, + "newSshNudgeBody": { + "message": "Store your keys and connect with the SSH agent for fast, encrypted authentication." } } diff --git a/apps/browser/src/_locales/ru/messages.json b/apps/browser/src/_locales/ru/messages.json index b0f03d34f69..f6343f6d2bb 100644 --- a/apps/browser/src/_locales/ru/messages.json +++ b/apps/browser/src/_locales/ru/messages.json @@ -2,6 +2,9 @@ "appName": { "message": "Bitwarden" }, + "appLogoLabel": { + "message": "Логотип Bitwarden" + }, "extName": { "message": "Bitwarden - Менеджер паролей", "description": "Extension name, MUST be less than 40 characters (Safari restriction)" @@ -883,6 +886,9 @@ "followTheStepsBelowToFinishLoggingIn": { "message": "Следуйте указаниям ниже, чтобы завершить авторизацию." }, + "followTheStepsBelowToFinishLoggingInWithSecurityKey": { + "message": "Выполните следующие шаги, чтобы завершить авторизацию с помощью ключа безопасности." + }, "restartRegistration": { "message": "Перезапустить регистрацию" }, @@ -1056,23 +1062,49 @@ "notificationAddSave": { "message": "Сохранить" }, - "loginSaveSuccessDetails": { - "message": "$USERNAME$ сохранен в Bitwarden.", + "notificationViewAria": { + "message": "Просмотр $ITEMNAME$, откроется в новом окне", "placeholders": { - "username": { + "itemName": { "content": "$1" } }, - "description": "Shown to user after login is saved." + "description": "Aria label for the view button in notification bar confirmation message" }, - "loginUpdatedSuccessDetails": { - "message": "$USERNAME$ обновлен в Bitwarden.", + "notificationEditTooltip": { + "message": "Изменить перед сохранением", + "description": "Tooltip and Aria label for edit button on cipher item" + }, + "newNotification": { + "message": "Новое уведомление" + }, + "labelWithNotification": { + "message": "$LABEL$: Новое уведомление", + "description": "Label for the notification with a new login suggestion.", "placeholders": { - "username": { + "label": { + "content": "$1", + "example": "Login" + } + } + }, + "loginSaveConfirmation": { + "message": "$ITEMNAME$ сохранен в Bitwarden.", + "placeholders": { + "itemName": { "content": "$1" } }, - "description": "Shown to user after login is updated." + "description": "Shown to user after item is saved." + }, + "loginUpdatedConfirmation": { + "message": "$ITEMNAME$ обновлен в Bitwarden.", + "placeholders": { + "itemName": { + "content": "$1" + } + }, + "description": "Shown to user after item is updated." }, "saveAsNewLoginAction": { "message": "Сохранить как новый логин", @@ -1082,12 +1114,12 @@ "message": "Обновить логин", "description": "Button text for updating an existing login entry." }, - "saveLoginPrompt": { - "message": "Сохранить логин?", + "saveLogin": { + "message": "Сохранить логин", "description": "Prompt asking the user if they want to save their login details." }, - "updateLoginPrompt": { - "message": "Обновить существующий логин?", + "updateLogin": { + "message": "Обновить существующий логин", "description": "Prompt asking the user if they want to update an existing login entry." }, "loginSaveSuccess": { @@ -4928,8 +4960,8 @@ "message": "Пароль сгенерирован", "description": "Notification message for when a password has been regenerated" }, - "saveLoginToBitwarden": { - "message": "Сохранить логин в Bitwarden?", + "saveToBitwarden": { + "message": "Сохранить в Bitwarden", "description": "Confirmation message for saving a login to Bitwarden" }, "spaceCharacterDescriptor": { @@ -5169,6 +5201,12 @@ "changeAtRiskPassword": { "message": "Изменить пароль, подверженный риску" }, + "settingsVaultOptions": { + "message": "Настройки хранилища" + }, + "emptyVaultDescription": { + "message": "Хранилище защищает не только ваши пароли. Логины, идентификаторы, карты и заметки в нем надежно защищены." + }, "introCarouselLabel": { "message": "Добро пожаловать в Bitwarden" }, @@ -5195,5 +5233,50 @@ }, "secureDevicesBody": { "message": "Сохраняйте неограниченное количество паролей на неограниченном количестве устройств с помощью мобильных, браузерных и десктопных приложений Bitwarden." + }, + "emptyVaultNudgeTitle": { + "message": "Импорт существующих паролей" + }, + "emptyVaultNudgeBody": { + "message": "Используйте импортер, чтобы быстро перенести логины в Bitwarden без их ручного добавления." + }, + "emptyVaultNudgeButton": { + "message": "Импортировать сейчас" + }, + "hasItemsVaultNudgeTitle": { + "message": "Добро пожаловать в ваше хранилище!" + }, + "hasItemsVaultNudgeBody": { + "message": "Автозаполнение элементов для текущей страницы\nИзбранные элементы для легкого доступа\nПоиск в хранилище для чего-либо еще" + }, + "newLoginNudgeTitle": { + "message": "Экономьте время с помощью автозаполнения" + }, + "newLoginNudgeBody": { + "message": "Включите сайт, чтобы этот логин отображался в качестве предложения для автозаполнения." + }, + "newCardNudgeTitle": { + "message": "Оформление заказа через интернет" + }, + "newCardNudgeBody": { + "message": "С помощью карт можно легко и безопасно автоматически заполнять формы оплаты." + }, + "newIdentityNudgeTitle": { + "message": "Упрощение создания аккаунтов" + }, + "newIdentityNudgeBody": { + "message": "С помощью личностей можно быстро заполнять длинные регистрационные или контактные формы." + }, + "newNoteNudgeTitle": { + "message": "Храните ваши конфиденциальные данные в безопасности" + }, + "newNoteNudgeBody": { + "message": "С помощью заметок можно надежно хранить конфиденциальные данные, например, банковские или страховые реквизиты." + }, + "newSshNudgeTitle": { + "message": "Удобный для разработчиков SSH-доступ" + }, + "newSshNudgeBody": { + "message": "Храните свои ключи и подключайтесь с помощью агента SSH для быстрой и зашифрованной аутентификации." } } diff --git a/apps/browser/src/_locales/si/messages.json b/apps/browser/src/_locales/si/messages.json index 4efbb2dceeb..a5f7e772505 100644 --- a/apps/browser/src/_locales/si/messages.json +++ b/apps/browser/src/_locales/si/messages.json @@ -2,6 +2,9 @@ "appName": { "message": "බිට්වාඩන්" }, + "appLogoLabel": { + "message": "Bitwarden logo" + }, "extName": { "message": "Bitwarden Password Manager", "description": "Extension name, MUST be less than 40 characters (Safari restriction)" @@ -883,6 +886,9 @@ "followTheStepsBelowToFinishLoggingIn": { "message": "Follow the steps below to finish logging in." }, + "followTheStepsBelowToFinishLoggingInWithSecurityKey": { + "message": "Follow the steps below to finish logging in with your security key." + }, "restartRegistration": { "message": "Restart registration" }, @@ -1056,23 +1062,49 @@ "notificationAddSave": { "message": "සුරකින්න" }, - "loginSaveSuccessDetails": { - "message": "$USERNAME$ saved to Bitwarden.", + "notificationViewAria": { + "message": "View $ITEMNAME$, opens in new window", "placeholders": { - "username": { + "itemName": { "content": "$1" } }, - "description": "Shown to user after login is saved." + "description": "Aria label for the view button in notification bar confirmation message" }, - "loginUpdatedSuccessDetails": { - "message": "$USERNAME$ updated in Bitwarden.", + "notificationEditTooltip": { + "message": "Edit before saving", + "description": "Tooltip and Aria label for edit button on cipher item" + }, + "newNotification": { + "message": "New notification" + }, + "labelWithNotification": { + "message": "$LABEL$: New notification", + "description": "Label for the notification with a new login suggestion.", "placeholders": { - "username": { + "label": { + "content": "$1", + "example": "Login" + } + } + }, + "loginSaveConfirmation": { + "message": "$ITEMNAME$ saved to Bitwarden.", + "placeholders": { + "itemName": { "content": "$1" } }, - "description": "Shown to user after login is updated." + "description": "Shown to user after item is saved." + }, + "loginUpdatedConfirmation": { + "message": "$ITEMNAME$ updated in Bitwarden.", + "placeholders": { + "itemName": { + "content": "$1" + } + }, + "description": "Shown to user after item is updated." }, "saveAsNewLoginAction": { "message": "Save as new login", @@ -1082,12 +1114,12 @@ "message": "Update login", "description": "Button text for updating an existing login entry." }, - "saveLoginPrompt": { - "message": "Save login?", + "saveLogin": { + "message": "Save login", "description": "Prompt asking the user if they want to save their login details." }, - "updateLoginPrompt": { - "message": "Update existing login?", + "updateLogin": { + "message": "Update existing login", "description": "Prompt asking the user if they want to update an existing login entry." }, "loginSaveSuccess": { @@ -4928,8 +4960,8 @@ "message": "Password regenerated", "description": "Notification message for when a password has been regenerated" }, - "saveLoginToBitwarden": { - "message": "Save login to Bitwarden?", + "saveToBitwarden": { + "message": "Save to Bitwarden", "description": "Confirmation message for saving a login to Bitwarden" }, "spaceCharacterDescriptor": { @@ -5169,6 +5201,12 @@ "changeAtRiskPassword": { "message": "Change at-risk password" }, + "settingsVaultOptions": { + "message": "Vault options" + }, + "emptyVaultDescription": { + "message": "The vault protects more than just your passwords. Store secure logins, IDs, cards and notes securely here." + }, "introCarouselLabel": { "message": "Welcome to Bitwarden" }, @@ -5195,5 +5233,50 @@ }, "secureDevicesBody": { "message": "Save unlimited passwords across unlimited devices with Bitwarden mobile, browser, and desktop apps." + }, + "emptyVaultNudgeTitle": { + "message": "Import existing passwords" + }, + "emptyVaultNudgeBody": { + "message": "Use the importer to quickly transfer logins to Bitwarden without manually adding them." + }, + "emptyVaultNudgeButton": { + "message": "Import now" + }, + "hasItemsVaultNudgeTitle": { + "message": "Welcome to your vault!" + }, + "hasItemsVaultNudgeBody": { + "message": "Autofill items for the current page\nFavorite items for easy access\nSearch your vault for something else" + }, + "newLoginNudgeTitle": { + "message": "Save time with autofill" + }, + "newLoginNudgeBody": { + "message": "Include a Website so this login appears as an autofill suggestion." + }, + "newCardNudgeTitle": { + "message": "Seamless online checkout" + }, + "newCardNudgeBody": { + "message": "With cards, easily autofill payment forms securely and accurately." + }, + "newIdentityNudgeTitle": { + "message": "Simplify creating accounts" + }, + "newIdentityNudgeBody": { + "message": "With identities, quickly autofill long registration or contact forms." + }, + "newNoteNudgeTitle": { + "message": "Keep your sensitive data safe" + }, + "newNoteNudgeBody": { + "message": "With notes, securely store sensitive data like banking or insurance details." + }, + "newSshNudgeTitle": { + "message": "Developer-friendly SSH access" + }, + "newSshNudgeBody": { + "message": "Store your keys and connect with the SSH agent for fast, encrypted authentication." } } diff --git a/apps/browser/src/_locales/sk/messages.json b/apps/browser/src/_locales/sk/messages.json index b427e902334..5f65633e44c 100644 --- a/apps/browser/src/_locales/sk/messages.json +++ b/apps/browser/src/_locales/sk/messages.json @@ -2,6 +2,9 @@ "appName": { "message": "Bitwarden" }, + "appLogoLabel": { + "message": "Logo Bitwarden" + }, "extName": { "message": "Bitwarden – Bezplatný správca hesiel", "description": "Extension name, MUST be less than 40 characters (Safari restriction)" @@ -883,6 +886,9 @@ "followTheStepsBelowToFinishLoggingIn": { "message": "Na dokončenie prihlásenia postupujte podľa pokynov." }, + "followTheStepsBelowToFinishLoggingInWithSecurityKey": { + "message": "Podľa nasledujúcich krokov dokončite prihlásenie pomocou bezpečnostného kľúča." + }, "restartRegistration": { "message": "Zopakovať registráciu" }, @@ -1056,23 +1062,49 @@ "notificationAddSave": { "message": "Uložiť" }, - "loginSaveSuccessDetails": { - "message": "$USERNAME$ uložené do Bitwarden.", + "notificationViewAria": { + "message": "Zobraziť $ITEMNAME$, otvorí sa v novom okne", "placeholders": { - "username": { + "itemName": { "content": "$1" } }, - "description": "Shown to user after login is saved." + "description": "Aria label for the view button in notification bar confirmation message" }, - "loginUpdatedSuccessDetails": { - "message": "$USERNAME$ aktualizované v Bitwarden.", + "notificationEditTooltip": { + "message": "Upraviť pred uložením", + "description": "Tooltip and Aria label for edit button on cipher item" + }, + "newNotification": { + "message": "Nové upozornenie" + }, + "labelWithNotification": { + "message": "$LABEL$: Nové upozornenie", + "description": "Label for the notification with a new login suggestion.", "placeholders": { - "username": { + "label": { + "content": "$1", + "example": "Login" + } + } + }, + "loginSaveConfirmation": { + "message": "$ITEMNAME$ uložené do Bitwarden.", + "placeholders": { + "itemName": { "content": "$1" } }, - "description": "Shown to user after login is updated." + "description": "Shown to user after item is saved." + }, + "loginUpdatedConfirmation": { + "message": "$ITEMNAME$ aktualizované v Bitwarden.", + "placeholders": { + "itemName": { + "content": "$1" + } + }, + "description": "Shown to user after item is updated." }, "saveAsNewLoginAction": { "message": "Uložiť ako nové prihlasovacie údaje", @@ -1082,12 +1114,12 @@ "message": "Aktualizovať prihlasovacie údaje", "description": "Button text for updating an existing login entry." }, - "saveLoginPrompt": { - "message": "Uložiť prihlasovacie údaje?", + "saveLogin": { + "message": "Uložiť prihlasovacie údaje", "description": "Prompt asking the user if they want to save their login details." }, - "updateLoginPrompt": { - "message": "Aktualizovať existujúce prihlasovacie údaje?", + "updateLogin": { + "message": "Aktualizovať existujúce prihlasovacie údaje", "description": "Prompt asking the user if they want to update an existing login entry." }, "loginSaveSuccess": { @@ -4649,7 +4681,7 @@ "message": "Textové polia používajte pre také údaje, ako sú bezpečnostné otázky" }, "hiddenHelpText": { - "message": "Skryté polia požívajte pre citlivé údaje ako je heslo" + "message": "Skryté polia požívajte pre citlivé údaje, ako je heslo" }, "checkBoxHelpText": { "message": "Ak chcete automaticky vyplniť začiarkávacie políčko formulára, napríklad zapamätať e-mail, použite začiarkávacie políčka" @@ -4928,8 +4960,8 @@ "message": "Vygenerované nové heslo", "description": "Notification message for when a password has been regenerated" }, - "saveLoginToBitwarden": { - "message": "Uložiť prihlasovacie údaje do Bitwardenu?", + "saveToBitwarden": { + "message": "Uložiť do Bitwardenu", "description": "Confirmation message for saving a login to Bitwarden" }, "spaceCharacterDescriptor": { @@ -5169,6 +5201,12 @@ "changeAtRiskPassword": { "message": "Zmeniť rizikové heslá" }, + "settingsVaultOptions": { + "message": "Možnosti trezoru" + }, + "emptyVaultDescription": { + "message": "Trezor chráni viac ako len heslá. Môžete tu bezpečne ukladať prihlasovacie údaje, identifikačné údaje, karty a poznámky." + }, "introCarouselLabel": { "message": "Vitajte v Bitwardene" }, @@ -5195,5 +5233,50 @@ }, "secureDevicesBody": { "message": "Ukladajte neobmedzený počet hesiel na neobmedzenom počte zariadení pomocou mobilných aplikácií, prehliadačov a desktopových aplikácií Bitwardenu." + }, + "emptyVaultNudgeTitle": { + "message": "Import existujúcich hesiel" + }, + "emptyVaultNudgeBody": { + "message": "Pomocou nástroja na import môžete rýchlo preniesť prihlasovacie údaje do Bitwardenu bez ručného pridávania." + }, + "emptyVaultNudgeButton": { + "message": "Importovať teraz" + }, + "hasItemsVaultNudgeTitle": { + "message": "Vitajte vo svojom trezore!" + }, + "hasItemsVaultNudgeBody": { + "message": "Automatické vypĺňanie položiek pre aktuálnu stránku\nObľúbené položky pre ľahký prístup\nVyhľadajte v trezore niečo iné" + }, + "newLoginNudgeTitle": { + "message": "Ušetrite čas s automatickým vypĺňaním" + }, + "newLoginNudgeBody": { + "message": "Zadajte webovú stránku, aby sa tieto prihlasovacie údaje zobrazili ako návrh na automatické vyplnenie." + }, + "newCardNudgeTitle": { + "message": "Bezproblémová online registrácia" + }, + "newCardNudgeBody": { + "message": "S kartami môžete jednoducho, bezpečne a presne automaticky vypĺňať platobné formuláre." + }, + "newIdentityNudgeTitle": { + "message": "Zjednodušenie vytvárania účtov" + }, + "newIdentityNudgeBody": { + "message": "Pomocou identít môžete rýchlo automaticky vypĺňať dlhé registračné alebo kontaktné formuláre." + }, + "newNoteNudgeTitle": { + "message": "Udržujte svoje citlivé údaje v bezpečí" + }, + "newNoteNudgeBody": { + "message": "Pomocou poznámok môžete bezpečne ukladať citlivé údaje, napríklad bankové údaje alebo údaje o poistení." + }, + "newSshNudgeTitle": { + "message": "Prístup SSH vhodný pre vývojárov" + }, + "newSshNudgeBody": { + "message": "Uložte si kľúče a pripojte sa pomocou agenta SSH na rýchle šifrované overovanie." } } diff --git a/apps/browser/src/_locales/sl/messages.json b/apps/browser/src/_locales/sl/messages.json index 61a70650d16..c7585878d8a 100644 --- a/apps/browser/src/_locales/sl/messages.json +++ b/apps/browser/src/_locales/sl/messages.json @@ -2,6 +2,9 @@ "appName": { "message": "Bitwarden" }, + "appLogoLabel": { + "message": "Bitwarden logo" + }, "extName": { "message": "Bitwarden Password Manager", "description": "Extension name, MUST be less than 40 characters (Safari restriction)" @@ -883,6 +886,9 @@ "followTheStepsBelowToFinishLoggingIn": { "message": "Follow the steps below to finish logging in." }, + "followTheStepsBelowToFinishLoggingInWithSecurityKey": { + "message": "Follow the steps below to finish logging in with your security key." + }, "restartRegistration": { "message": "Restart registration" }, @@ -1056,23 +1062,49 @@ "notificationAddSave": { "message": "Da, shrani zdaj" }, - "loginSaveSuccessDetails": { - "message": "$USERNAME$ saved to Bitwarden.", + "notificationViewAria": { + "message": "View $ITEMNAME$, opens in new window", "placeholders": { - "username": { + "itemName": { "content": "$1" } }, - "description": "Shown to user after login is saved." + "description": "Aria label for the view button in notification bar confirmation message" }, - "loginUpdatedSuccessDetails": { - "message": "$USERNAME$ updated in Bitwarden.", + "notificationEditTooltip": { + "message": "Edit before saving", + "description": "Tooltip and Aria label for edit button on cipher item" + }, + "newNotification": { + "message": "New notification" + }, + "labelWithNotification": { + "message": "$LABEL$: New notification", + "description": "Label for the notification with a new login suggestion.", "placeholders": { - "username": { + "label": { + "content": "$1", + "example": "Login" + } + } + }, + "loginSaveConfirmation": { + "message": "$ITEMNAME$ saved to Bitwarden.", + "placeholders": { + "itemName": { "content": "$1" } }, - "description": "Shown to user after login is updated." + "description": "Shown to user after item is saved." + }, + "loginUpdatedConfirmation": { + "message": "$ITEMNAME$ updated in Bitwarden.", + "placeholders": { + "itemName": { + "content": "$1" + } + }, + "description": "Shown to user after item is updated." }, "saveAsNewLoginAction": { "message": "Save as new login", @@ -1082,12 +1114,12 @@ "message": "Update login", "description": "Button text for updating an existing login entry." }, - "saveLoginPrompt": { - "message": "Save login?", + "saveLogin": { + "message": "Save login", "description": "Prompt asking the user if they want to save their login details." }, - "updateLoginPrompt": { - "message": "Update existing login?", + "updateLogin": { + "message": "Update existing login", "description": "Prompt asking the user if they want to update an existing login entry." }, "loginSaveSuccess": { @@ -4928,8 +4960,8 @@ "message": "Password regenerated", "description": "Notification message for when a password has been regenerated" }, - "saveLoginToBitwarden": { - "message": "Save login to Bitwarden?", + "saveToBitwarden": { + "message": "Save to Bitwarden", "description": "Confirmation message for saving a login to Bitwarden" }, "spaceCharacterDescriptor": { @@ -5169,6 +5201,12 @@ "changeAtRiskPassword": { "message": "Change at-risk password" }, + "settingsVaultOptions": { + "message": "Vault options" + }, + "emptyVaultDescription": { + "message": "The vault protects more than just your passwords. Store secure logins, IDs, cards and notes securely here." + }, "introCarouselLabel": { "message": "Welcome to Bitwarden" }, @@ -5195,5 +5233,50 @@ }, "secureDevicesBody": { "message": "Save unlimited passwords across unlimited devices with Bitwarden mobile, browser, and desktop apps." + }, + "emptyVaultNudgeTitle": { + "message": "Import existing passwords" + }, + "emptyVaultNudgeBody": { + "message": "Use the importer to quickly transfer logins to Bitwarden without manually adding them." + }, + "emptyVaultNudgeButton": { + "message": "Import now" + }, + "hasItemsVaultNudgeTitle": { + "message": "Welcome to your vault!" + }, + "hasItemsVaultNudgeBody": { + "message": "Autofill items for the current page\nFavorite items for easy access\nSearch your vault for something else" + }, + "newLoginNudgeTitle": { + "message": "Save time with autofill" + }, + "newLoginNudgeBody": { + "message": "Include a Website so this login appears as an autofill suggestion." + }, + "newCardNudgeTitle": { + "message": "Seamless online checkout" + }, + "newCardNudgeBody": { + "message": "With cards, easily autofill payment forms securely and accurately." + }, + "newIdentityNudgeTitle": { + "message": "Simplify creating accounts" + }, + "newIdentityNudgeBody": { + "message": "With identities, quickly autofill long registration or contact forms." + }, + "newNoteNudgeTitle": { + "message": "Keep your sensitive data safe" + }, + "newNoteNudgeBody": { + "message": "With notes, securely store sensitive data like banking or insurance details." + }, + "newSshNudgeTitle": { + "message": "Developer-friendly SSH access" + }, + "newSshNudgeBody": { + "message": "Store your keys and connect with the SSH agent for fast, encrypted authentication." } } diff --git a/apps/browser/src/_locales/sr/messages.json b/apps/browser/src/_locales/sr/messages.json index a36006894ba..a5b18b0da21 100644 --- a/apps/browser/src/_locales/sr/messages.json +++ b/apps/browser/src/_locales/sr/messages.json @@ -2,6 +2,9 @@ "appName": { "message": "Bitwarden" }, + "appLogoLabel": { + "message": "Bitwarden лого" + }, "extName": { "message": "Bitwarden Менаџер Лозинке", "description": "Extension name, MUST be less than 40 characters (Safari restriction)" @@ -883,6 +886,9 @@ "followTheStepsBelowToFinishLoggingIn": { "message": "Следите наведене кораке да бисте завршили пријављивање." }, + "followTheStepsBelowToFinishLoggingInWithSecurityKey": { + "message": "Следите наведене кораке да бисте завршили пријаву са својим безбедносним кључем." + }, "restartRegistration": { "message": "Поново покрените регистрацију" }, @@ -1056,23 +1062,49 @@ "notificationAddSave": { "message": "Сачувај" }, - "loginSaveSuccessDetails": { - "message": "$USERNAME$ сачуван и Bitwarden.", + "notificationViewAria": { + "message": "Преглед $ITEMNAME$, отвара се у новом прозору", "placeholders": { - "username": { + "itemName": { "content": "$1" } }, - "description": "Shown to user after login is saved." + "description": "Aria label for the view button in notification bar confirmation message" }, - "loginUpdatedSuccessDetails": { - "message": "$USERNAME$ ажурирано у Bitwarden.", + "notificationEditTooltip": { + "message": "Уреди пре сачувавања", + "description": "Tooltip and Aria label for edit button on cipher item" + }, + "newNotification": { + "message": "Ново обавештење" + }, + "labelWithNotification": { + "message": "$LABEL$: Ново обавештење", + "description": "Label for the notification with a new login suggestion.", "placeholders": { - "username": { + "label": { + "content": "$1", + "example": "Login" + } + } + }, + "loginSaveConfirmation": { + "message": "$ITEMNAME$ сачувано и Bitwarden.", + "placeholders": { + "itemName": { "content": "$1" } }, - "description": "Shown to user after login is updated." + "description": "Shown to user after item is saved." + }, + "loginUpdatedConfirmation": { + "message": "$ITEMNAME$ ажурирано у Bitwarden.", + "placeholders": { + "itemName": { + "content": "$1" + } + }, + "description": "Shown to user after item is updated." }, "saveAsNewLoginAction": { "message": "Сачувати као нову пријаву", @@ -1082,12 +1114,12 @@ "message": "Ажурирати пријаву", "description": "Button text for updating an existing login entry." }, - "saveLoginPrompt": { - "message": "Сачувати пријаву?", + "saveLogin": { + "message": "Сачувати пријаву", "description": "Prompt asking the user if they want to save their login details." }, - "updateLoginPrompt": { - "message": "Ажурирајте постојећу пријаву?", + "updateLogin": { + "message": "Ажурирати постојећу пријаву", "description": "Prompt asking the user if they want to update an existing login entry." }, "loginSaveSuccess": { @@ -1099,7 +1131,7 @@ "description": "Message displayed when login details are successfully updated." }, "loginUpdateTaskSuccess": { - "message": "Great job! You took the steps to make you and $ORGANIZATION$ more secure.", + "message": "Одличан посао! Узели сте кораке да ви и $ORGANIZATION$ будете сигурнији.", "placeholders": { "organization": { "content": "$1" @@ -1108,7 +1140,7 @@ "description": "Shown to user after login is updated." }, "loginUpdateTaskSuccessAdditional": { - "message": "Thank you for making $ORGANIZATION$ more secure. You have $TASK_COUNT$ more passwords to update.", + "message": "Хвала вам што сте осигурали $ORGANIZATION$. Имате још $TASK_COUNT$ лозинки за ажурирање.", "placeholders": { "organization": { "content": "$1" @@ -1120,7 +1152,7 @@ "description": "Shown to user after login is updated." }, "nextSecurityTaskAction": { - "message": "Change next password", + "message": "Променити следећу лозинку", "description": "Message prompting user to undertake completion of another security task." }, "saveFailure": { @@ -2323,7 +2355,7 @@ "message": "Политика приватности" }, "yourNewPasswordCannotBeTheSameAsYourCurrentPassword": { - "message": "Your new password cannot be the same as your current password." + "message": "Ваша нова лозинка не може бити иста као тренутна лозинка." }, "hintEqualsPassword": { "message": "Ваш савет за лозинку не може да буде исти као лозинка." @@ -4928,8 +4960,8 @@ "message": "Лозинка поново генерисана", "description": "Notification message for when a password has been regenerated" }, - "saveLoginToBitwarden": { - "message": "Сачувати пријаву на Bitwarden?", + "saveToBitwarden": { + "message": "Сачувај у Bitwarden", "description": "Confirmation message for saving a login to Bitwarden" }, "spaceCharacterDescriptor": { @@ -5169,31 +5201,82 @@ "changeAtRiskPassword": { "message": "Променити ризичну лозинку" }, + "settingsVaultOptions": { + "message": "Опције сефа" + }, + "emptyVaultDescription": { + "message": "Сеф штити више од само ваших лозинки. Овде безбедно чувајте безбедне пријаве, личне карте, картице и белешке." + }, "introCarouselLabel": { - "message": "Welcome to Bitwarden" + "message": "Добродошли у Bitwarden" }, "securityPrioritized": { - "message": "Security, prioritized" + "message": "Сигурност, приоритет" }, "securityPrioritizedBody": { - "message": "Save logins, cards, and identities to your secure vault. Bitwarden uses zero-knowledge, end-to-end encryption to protect what’s important to you." + "message": "Сачувај пријаве, картице и идентитете у свом безбедном сефу. Bitwarden користи шифровање од почетка-до-краја да заштити оно што вам је важно." }, "quickLogin": { - "message": "Quick and easy login" + "message": "Брза и лака пријава" }, "quickLoginBody": { - "message": "Set up biometric unlock and autofill to log into your accounts without typing a single letter." + "message": "Подесите биометријско откључавање и аутоматско попуњавање да бисте се пријавили на своје налоге без уноса ниједног слова." }, "secureUser": { - "message": "Level up your logins" + "message": "Подигните ниво својих пријава" }, "secureUserBody": { - "message": "Use the generator to create and save strong, unique passwords for all your accounts." + "message": "Користите генератор да креирате и сачувате јаке, јединствене лозинке за све ваше налоге." }, "secureDevices": { - "message": "Your data, when and where you need it" + "message": "Ваши подаци, када и где су вам потребни" }, "secureDevicesBody": { - "message": "Save unlimited passwords across unlimited devices with Bitwarden mobile, browser, and desktop apps." + "message": "Сачувајте неограничене лозинке на неограниченим уређајима помоћу Bitwarden мобилних апликација, претраживача и десктоп апликација." + }, + "emptyVaultNudgeTitle": { + "message": "Увоз постојеће лозинке" + }, + "emptyVaultNudgeBody": { + "message": "Користите увозник да брзо преносите пријаве у Bitwarden без да их ручно додате." + }, + "emptyVaultNudgeButton": { + "message": "Увези сада" + }, + "hasItemsVaultNudgeTitle": { + "message": "Добродошли у ваш сеф!" + }, + "hasItemsVaultNudgeBody": { + "message": "Ауто-пуњење предмета за тренутну страницу\nОмиљени предмети за лак приступ\nПретражите сеф за нешто друго" + }, + "newLoginNudgeTitle": { + "message": "Уштедите време са ауто-пуњењем" + }, + "newLoginNudgeBody": { + "message": "Укључите веб страницу тако да се ова пријава појављује као предлог за ауто-пуњење." + }, + "newCardNudgeTitle": { + "message": "Seamless online checkout" + }, + "newCardNudgeBody": { + "message": "Са картицама, лако и сигурносно попуните формуларе за плаћање." + }, + "newIdentityNudgeTitle": { + "message": "Поједноставите креирање налога" + }, + "newIdentityNudgeBody": { + "message": "Са идентитетима, брзо попуните регистрације или контактних образаци." + }, + "newNoteNudgeTitle": { + "message": "Држите на сигурном своје осетљиве податке" + }, + "newNoteNudgeBody": { + "message": "Са белешкама, сигурно чувајте осетљиве податке попут банкарског или подаци о осигурању." + }, + "newSshNudgeTitle": { + "message": "Лак SSH приступ" + }, + "newSshNudgeBody": { + "message": "Чувајте кључеве и повежите се са SSH агент за брзу, шифровану аутентификацију." } } diff --git a/apps/browser/src/_locales/sv/messages.json b/apps/browser/src/_locales/sv/messages.json index 54a34e15013..27443c64140 100644 --- a/apps/browser/src/_locales/sv/messages.json +++ b/apps/browser/src/_locales/sv/messages.json @@ -2,6 +2,9 @@ "appName": { "message": "Bitwarden" }, + "appLogoLabel": { + "message": "Bitwarden logo" + }, "extName": { "message": "Bitwarden Lösenordshanterare", "description": "Extension name, MUST be less than 40 characters (Safari restriction)" @@ -883,6 +886,9 @@ "followTheStepsBelowToFinishLoggingIn": { "message": "Follow the steps below to finish logging in." }, + "followTheStepsBelowToFinishLoggingInWithSecurityKey": { + "message": "Follow the steps below to finish logging in with your security key." + }, "restartRegistration": { "message": "Restart registration" }, @@ -1056,23 +1062,49 @@ "notificationAddSave": { "message": "Spara" }, - "loginSaveSuccessDetails": { - "message": "$USERNAME$ saved to Bitwarden.", + "notificationViewAria": { + "message": "View $ITEMNAME$, opens in new window", "placeholders": { - "username": { + "itemName": { "content": "$1" } }, - "description": "Shown to user after login is saved." + "description": "Aria label for the view button in notification bar confirmation message" }, - "loginUpdatedSuccessDetails": { - "message": "$USERNAME$ updated in Bitwarden.", + "notificationEditTooltip": { + "message": "Edit before saving", + "description": "Tooltip and Aria label for edit button on cipher item" + }, + "newNotification": { + "message": "New notification" + }, + "labelWithNotification": { + "message": "$LABEL$: New notification", + "description": "Label for the notification with a new login suggestion.", "placeholders": { - "username": { + "label": { + "content": "$1", + "example": "Login" + } + } + }, + "loginSaveConfirmation": { + "message": "$ITEMNAME$ saved to Bitwarden.", + "placeholders": { + "itemName": { "content": "$1" } }, - "description": "Shown to user after login is updated." + "description": "Shown to user after item is saved." + }, + "loginUpdatedConfirmation": { + "message": "$ITEMNAME$ updated in Bitwarden.", + "placeholders": { + "itemName": { + "content": "$1" + } + }, + "description": "Shown to user after item is updated." }, "saveAsNewLoginAction": { "message": "Save as new login", @@ -1082,12 +1114,12 @@ "message": "Update login", "description": "Button text for updating an existing login entry." }, - "saveLoginPrompt": { - "message": "Save login?", + "saveLogin": { + "message": "Save login", "description": "Prompt asking the user if they want to save their login details." }, - "updateLoginPrompt": { - "message": "Update existing login?", + "updateLogin": { + "message": "Update existing login", "description": "Prompt asking the user if they want to update an existing login entry." }, "loginSaveSuccess": { @@ -4928,8 +4960,8 @@ "message": "Password regenerated", "description": "Notification message for when a password has been regenerated" }, - "saveLoginToBitwarden": { - "message": "Spara inloggning på Bitwarden?", + "saveToBitwarden": { + "message": "Save to Bitwarden", "description": "Confirmation message for saving a login to Bitwarden" }, "spaceCharacterDescriptor": { @@ -5169,6 +5201,12 @@ "changeAtRiskPassword": { "message": "Change at-risk password" }, + "settingsVaultOptions": { + "message": "Vault options" + }, + "emptyVaultDescription": { + "message": "The vault protects more than just your passwords. Store secure logins, IDs, cards and notes securely here." + }, "introCarouselLabel": { "message": "Welcome to Bitwarden" }, @@ -5195,5 +5233,50 @@ }, "secureDevicesBody": { "message": "Save unlimited passwords across unlimited devices with Bitwarden mobile, browser, and desktop apps." + }, + "emptyVaultNudgeTitle": { + "message": "Import existing passwords" + }, + "emptyVaultNudgeBody": { + "message": "Use the importer to quickly transfer logins to Bitwarden without manually adding them." + }, + "emptyVaultNudgeButton": { + "message": "Import now" + }, + "hasItemsVaultNudgeTitle": { + "message": "Welcome to your vault!" + }, + "hasItemsVaultNudgeBody": { + "message": "Autofill items for the current page\nFavorite items for easy access\nSearch your vault for something else" + }, + "newLoginNudgeTitle": { + "message": "Save time with autofill" + }, + "newLoginNudgeBody": { + "message": "Include a Website so this login appears as an autofill suggestion." + }, + "newCardNudgeTitle": { + "message": "Seamless online checkout" + }, + "newCardNudgeBody": { + "message": "With cards, easily autofill payment forms securely and accurately." + }, + "newIdentityNudgeTitle": { + "message": "Simplify creating accounts" + }, + "newIdentityNudgeBody": { + "message": "With identities, quickly autofill long registration or contact forms." + }, + "newNoteNudgeTitle": { + "message": "Keep your sensitive data safe" + }, + "newNoteNudgeBody": { + "message": "With notes, securely store sensitive data like banking or insurance details." + }, + "newSshNudgeTitle": { + "message": "Developer-friendly SSH access" + }, + "newSshNudgeBody": { + "message": "Store your keys and connect with the SSH agent for fast, encrypted authentication." } } diff --git a/apps/browser/src/_locales/te/messages.json b/apps/browser/src/_locales/te/messages.json index 7bec167e1c6..99a09c75d41 100644 --- a/apps/browser/src/_locales/te/messages.json +++ b/apps/browser/src/_locales/te/messages.json @@ -2,6 +2,9 @@ "appName": { "message": "Bitwarden" }, + "appLogoLabel": { + "message": "Bitwarden logo" + }, "extName": { "message": "Bitwarden Password Manager", "description": "Extension name, MUST be less than 40 characters (Safari restriction)" @@ -883,6 +886,9 @@ "followTheStepsBelowToFinishLoggingIn": { "message": "Follow the steps below to finish logging in." }, + "followTheStepsBelowToFinishLoggingInWithSecurityKey": { + "message": "Follow the steps below to finish logging in with your security key." + }, "restartRegistration": { "message": "Restart registration" }, @@ -1056,23 +1062,49 @@ "notificationAddSave": { "message": "Save" }, - "loginSaveSuccessDetails": { - "message": "$USERNAME$ saved to Bitwarden.", + "notificationViewAria": { + "message": "View $ITEMNAME$, opens in new window", "placeholders": { - "username": { + "itemName": { "content": "$1" } }, - "description": "Shown to user after login is saved." + "description": "Aria label for the view button in notification bar confirmation message" }, - "loginUpdatedSuccessDetails": { - "message": "$USERNAME$ updated in Bitwarden.", + "notificationEditTooltip": { + "message": "Edit before saving", + "description": "Tooltip and Aria label for edit button on cipher item" + }, + "newNotification": { + "message": "New notification" + }, + "labelWithNotification": { + "message": "$LABEL$: New notification", + "description": "Label for the notification with a new login suggestion.", "placeholders": { - "username": { + "label": { + "content": "$1", + "example": "Login" + } + } + }, + "loginSaveConfirmation": { + "message": "$ITEMNAME$ saved to Bitwarden.", + "placeholders": { + "itemName": { "content": "$1" } }, - "description": "Shown to user after login is updated." + "description": "Shown to user after item is saved." + }, + "loginUpdatedConfirmation": { + "message": "$ITEMNAME$ updated in Bitwarden.", + "placeholders": { + "itemName": { + "content": "$1" + } + }, + "description": "Shown to user after item is updated." }, "saveAsNewLoginAction": { "message": "Save as new login", @@ -1082,12 +1114,12 @@ "message": "Update login", "description": "Button text for updating an existing login entry." }, - "saveLoginPrompt": { - "message": "Save login?", + "saveLogin": { + "message": "Save login", "description": "Prompt asking the user if they want to save their login details." }, - "updateLoginPrompt": { - "message": "Update existing login?", + "updateLogin": { + "message": "Update existing login", "description": "Prompt asking the user if they want to update an existing login entry." }, "loginSaveSuccess": { @@ -4928,8 +4960,8 @@ "message": "Password regenerated", "description": "Notification message for when a password has been regenerated" }, - "saveLoginToBitwarden": { - "message": "Save login to Bitwarden?", + "saveToBitwarden": { + "message": "Save to Bitwarden", "description": "Confirmation message for saving a login to Bitwarden" }, "spaceCharacterDescriptor": { @@ -5169,6 +5201,12 @@ "changeAtRiskPassword": { "message": "Change at-risk password" }, + "settingsVaultOptions": { + "message": "Vault options" + }, + "emptyVaultDescription": { + "message": "The vault protects more than just your passwords. Store secure logins, IDs, cards and notes securely here." + }, "introCarouselLabel": { "message": "Welcome to Bitwarden" }, @@ -5195,5 +5233,50 @@ }, "secureDevicesBody": { "message": "Save unlimited passwords across unlimited devices with Bitwarden mobile, browser, and desktop apps." + }, + "emptyVaultNudgeTitle": { + "message": "Import existing passwords" + }, + "emptyVaultNudgeBody": { + "message": "Use the importer to quickly transfer logins to Bitwarden without manually adding them." + }, + "emptyVaultNudgeButton": { + "message": "Import now" + }, + "hasItemsVaultNudgeTitle": { + "message": "Welcome to your vault!" + }, + "hasItemsVaultNudgeBody": { + "message": "Autofill items for the current page\nFavorite items for easy access\nSearch your vault for something else" + }, + "newLoginNudgeTitle": { + "message": "Save time with autofill" + }, + "newLoginNudgeBody": { + "message": "Include a Website so this login appears as an autofill suggestion." + }, + "newCardNudgeTitle": { + "message": "Seamless online checkout" + }, + "newCardNudgeBody": { + "message": "With cards, easily autofill payment forms securely and accurately." + }, + "newIdentityNudgeTitle": { + "message": "Simplify creating accounts" + }, + "newIdentityNudgeBody": { + "message": "With identities, quickly autofill long registration or contact forms." + }, + "newNoteNudgeTitle": { + "message": "Keep your sensitive data safe" + }, + "newNoteNudgeBody": { + "message": "With notes, securely store sensitive data like banking or insurance details." + }, + "newSshNudgeTitle": { + "message": "Developer-friendly SSH access" + }, + "newSshNudgeBody": { + "message": "Store your keys and connect with the SSH agent for fast, encrypted authentication." } } diff --git a/apps/browser/src/_locales/th/messages.json b/apps/browser/src/_locales/th/messages.json index b502e29cfdd..f6c8a9f0584 100644 --- a/apps/browser/src/_locales/th/messages.json +++ b/apps/browser/src/_locales/th/messages.json @@ -2,6 +2,9 @@ "appName": { "message": "bitwarden" }, + "appLogoLabel": { + "message": "Bitwarden logo" + }, "extName": { "message": "Bitwarden Password Manager", "description": "Extension name, MUST be less than 40 characters (Safari restriction)" @@ -883,6 +886,9 @@ "followTheStepsBelowToFinishLoggingIn": { "message": "Follow the steps below to finish logging in." }, + "followTheStepsBelowToFinishLoggingInWithSecurityKey": { + "message": "Follow the steps below to finish logging in with your security key." + }, "restartRegistration": { "message": "Restart registration" }, @@ -1056,23 +1062,49 @@ "notificationAddSave": { "message": "Yes, Save Now" }, - "loginSaveSuccessDetails": { - "message": "$USERNAME$ saved to Bitwarden.", + "notificationViewAria": { + "message": "View $ITEMNAME$, opens in new window", "placeholders": { - "username": { + "itemName": { "content": "$1" } }, - "description": "Shown to user after login is saved." + "description": "Aria label for the view button in notification bar confirmation message" }, - "loginUpdatedSuccessDetails": { - "message": "$USERNAME$ updated in Bitwarden.", + "notificationEditTooltip": { + "message": "Edit before saving", + "description": "Tooltip and Aria label for edit button on cipher item" + }, + "newNotification": { + "message": "New notification" + }, + "labelWithNotification": { + "message": "$LABEL$: New notification", + "description": "Label for the notification with a new login suggestion.", "placeholders": { - "username": { + "label": { + "content": "$1", + "example": "Login" + } + } + }, + "loginSaveConfirmation": { + "message": "$ITEMNAME$ saved to Bitwarden.", + "placeholders": { + "itemName": { "content": "$1" } }, - "description": "Shown to user after login is updated." + "description": "Shown to user after item is saved." + }, + "loginUpdatedConfirmation": { + "message": "$ITEMNAME$ updated in Bitwarden.", + "placeholders": { + "itemName": { + "content": "$1" + } + }, + "description": "Shown to user after item is updated." }, "saveAsNewLoginAction": { "message": "Save as new login", @@ -1082,12 +1114,12 @@ "message": "Update login", "description": "Button text for updating an existing login entry." }, - "saveLoginPrompt": { - "message": "Save login?", + "saveLogin": { + "message": "Save login", "description": "Prompt asking the user if they want to save their login details." }, - "updateLoginPrompt": { - "message": "Update existing login?", + "updateLogin": { + "message": "Update existing login", "description": "Prompt asking the user if they want to update an existing login entry." }, "loginSaveSuccess": { @@ -4928,8 +4960,8 @@ "message": "Password regenerated", "description": "Notification message for when a password has been regenerated" }, - "saveLoginToBitwarden": { - "message": "Save login to Bitwarden?", + "saveToBitwarden": { + "message": "Save to Bitwarden", "description": "Confirmation message for saving a login to Bitwarden" }, "spaceCharacterDescriptor": { @@ -5169,6 +5201,12 @@ "changeAtRiskPassword": { "message": "Change at-risk password" }, + "settingsVaultOptions": { + "message": "Vault options" + }, + "emptyVaultDescription": { + "message": "The vault protects more than just your passwords. Store secure logins, IDs, cards and notes securely here." + }, "introCarouselLabel": { "message": "Welcome to Bitwarden" }, @@ -5195,5 +5233,50 @@ }, "secureDevicesBody": { "message": "Save unlimited passwords across unlimited devices with Bitwarden mobile, browser, and desktop apps." + }, + "emptyVaultNudgeTitle": { + "message": "Import existing passwords" + }, + "emptyVaultNudgeBody": { + "message": "Use the importer to quickly transfer logins to Bitwarden without manually adding them." + }, + "emptyVaultNudgeButton": { + "message": "Import now" + }, + "hasItemsVaultNudgeTitle": { + "message": "Welcome to your vault!" + }, + "hasItemsVaultNudgeBody": { + "message": "Autofill items for the current page\nFavorite items for easy access\nSearch your vault for something else" + }, + "newLoginNudgeTitle": { + "message": "Save time with autofill" + }, + "newLoginNudgeBody": { + "message": "Include a Website so this login appears as an autofill suggestion." + }, + "newCardNudgeTitle": { + "message": "Seamless online checkout" + }, + "newCardNudgeBody": { + "message": "With cards, easily autofill payment forms securely and accurately." + }, + "newIdentityNudgeTitle": { + "message": "Simplify creating accounts" + }, + "newIdentityNudgeBody": { + "message": "With identities, quickly autofill long registration or contact forms." + }, + "newNoteNudgeTitle": { + "message": "Keep your sensitive data safe" + }, + "newNoteNudgeBody": { + "message": "With notes, securely store sensitive data like banking or insurance details." + }, + "newSshNudgeTitle": { + "message": "Developer-friendly SSH access" + }, + "newSshNudgeBody": { + "message": "Store your keys and connect with the SSH agent for fast, encrypted authentication." } } diff --git a/apps/browser/src/_locales/tr/messages.json b/apps/browser/src/_locales/tr/messages.json index 8a00287dfcc..052941f2281 100644 --- a/apps/browser/src/_locales/tr/messages.json +++ b/apps/browser/src/_locales/tr/messages.json @@ -2,6 +2,9 @@ "appName": { "message": "Bitwarden" }, + "appLogoLabel": { + "message": "Bitwarden logosu" + }, "extName": { "message": "Bitwarden Parola Yöneticisi", "description": "Extension name, MUST be less than 40 characters (Safari restriction)" @@ -883,6 +886,9 @@ "followTheStepsBelowToFinishLoggingIn": { "message": "Girişi tamamlamak için aşağıdaki adımları izleyin." }, + "followTheStepsBelowToFinishLoggingInWithSecurityKey": { + "message": "Güvenlik anahtarınızla girişi tamamlamak için aşağıdaki adımları izleyin." + }, "restartRegistration": { "message": "Kaydı yeniden başlat" }, @@ -1056,23 +1062,49 @@ "notificationAddSave": { "message": "Kaydet" }, - "loginSaveSuccessDetails": { - "message": "$USERNAME$ Bitwarden'a kaydedildi.", + "notificationViewAria": { + "message": "$ITEMNAME$ kaydını görüntüle. Yeni pencerede açılır", "placeholders": { - "username": { + "itemName": { "content": "$1" } }, - "description": "Shown to user after login is saved." + "description": "Aria label for the view button in notification bar confirmation message" }, - "loginUpdatedSuccessDetails": { - "message": "$USERNAME$ Bitwarden'da güncellendi.", + "notificationEditTooltip": { + "message": "Kaydetmeden önce düzenle", + "description": "Tooltip and Aria label for edit button on cipher item" + }, + "newNotification": { + "message": "Yeni bildirim" + }, + "labelWithNotification": { + "message": "$LABEL$: Yeni bildirim", + "description": "Label for the notification with a new login suggestion.", "placeholders": { - "username": { + "label": { + "content": "$1", + "example": "Login" + } + } + }, + "loginSaveConfirmation": { + "message": "$ITEMNAME$ Bitwarden'a kaydedildi.", + "placeholders": { + "itemName": { "content": "$1" } }, - "description": "Shown to user after login is updated." + "description": "Shown to user after item is saved." + }, + "loginUpdatedConfirmation": { + "message": "$ITEMNAME$ Bitwarden'da güncellendi.", + "placeholders": { + "itemName": { + "content": "$1" + } + }, + "description": "Shown to user after item is updated." }, "saveAsNewLoginAction": { "message": "Yeni hesap olarak kaydet", @@ -1082,12 +1114,12 @@ "message": "Hesabı güncelle", "description": "Button text for updating an existing login entry." }, - "saveLoginPrompt": { - "message": "Hesap kaydedilsin mi?", + "saveLogin": { + "message": "Hesabı kaydet", "description": "Prompt asking the user if they want to save their login details." }, - "updateLoginPrompt": { - "message": "Mevcut hesap güncellensin mi?", + "updateLogin": { + "message": "Mevcut hesabı güncelle", "description": "Prompt asking the user if they want to update an existing login entry." }, "loginSaveSuccess": { @@ -4928,8 +4960,8 @@ "message": "Parola yeniden üretildi", "description": "Notification message for when a password has been regenerated" }, - "saveLoginToBitwarden": { - "message": "Hesap Bitwarden'a kaydedilsin mi?", + "saveToBitwarden": { + "message": "Bitwarden’a kaydet", "description": "Confirmation message for saving a login to Bitwarden" }, "spaceCharacterDescriptor": { @@ -5169,6 +5201,12 @@ "changeAtRiskPassword": { "message": "Change at-risk password" }, + "settingsVaultOptions": { + "message": "Kasa seçenekleri" + }, + "emptyVaultDescription": { + "message": "Kasanız sadece parolalarınız için değil. Hesaplarınızı, kimliklerinizi, kredi kartlarınızı ve notlarınızı da güvenle burada depolayabilirsiniz." + }, "introCarouselLabel": { "message": "Bitwarden’a hoş geldiniz" }, @@ -5195,5 +5233,50 @@ }, "secureDevicesBody": { "message": "Bitwarden mobil, tarayıcı ve masaüstü uygulamalarıyla istediğiniz kadar cihaza istediğiniz kadar parola kaydedebilirsiniz." + }, + "emptyVaultNudgeTitle": { + "message": "Mevcut parolaları içe aktar" + }, + "emptyVaultNudgeBody": { + "message": "Use the importer to quickly transfer logins to Bitwarden without manually adding them." + }, + "emptyVaultNudgeButton": { + "message": "Şimdi içe aktar" + }, + "hasItemsVaultNudgeTitle": { + "message": "Kasanıza hoş geldiniz!" + }, + "hasItemsVaultNudgeBody": { + "message": "Autofill items for the current page\nFavorite items for easy access\nSearch your vault for something else" + }, + "newLoginNudgeTitle": { + "message": "Otomatik doldurmayla zaman kazanın" + }, + "newLoginNudgeBody": { + "message": "Include a Website so this login appears as an autofill suggestion." + }, + "newCardNudgeTitle": { + "message": "Kesintisiz çevrimiçi alışveriş" + }, + "newCardNudgeBody": { + "message": "With cards, easily autofill payment forms securely and accurately." + }, + "newIdentityNudgeTitle": { + "message": "Hesap oluşturmak artık daha basit" + }, + "newIdentityNudgeBody": { + "message": "With identities, quickly autofill long registration or contact forms." + }, + "newNoteNudgeTitle": { + "message": "Hassas verilerinizi güvende tutun" + }, + "newNoteNudgeBody": { + "message": "With notes, securely store sensitive data like banking or insurance details." + }, + "newSshNudgeTitle": { + "message": "Developer-friendly SSH access" + }, + "newSshNudgeBody": { + "message": "Store your keys and connect with the SSH agent for fast, encrypted authentication." } } diff --git a/apps/browser/src/_locales/uk/messages.json b/apps/browser/src/_locales/uk/messages.json index bdd96ba9ea4..7b62690d03c 100644 --- a/apps/browser/src/_locales/uk/messages.json +++ b/apps/browser/src/_locales/uk/messages.json @@ -2,6 +2,9 @@ "appName": { "message": "Bitwarden" }, + "appLogoLabel": { + "message": "Логотип Bitwarden" + }, "extName": { "message": "Bitwarden – менеджер паролів", "description": "Extension name, MUST be less than 40 characters (Safari restriction)" @@ -883,6 +886,9 @@ "followTheStepsBelowToFinishLoggingIn": { "message": "Виконайте наведені нижче кроки, щоб завершити вхід." }, + "followTheStepsBelowToFinishLoggingInWithSecurityKey": { + "message": "Виконайте наведені нижче кроки, щоб завершити вхід за допомогою ключа безпеки." + }, "restartRegistration": { "message": "Перезапустити реєстрацію" }, @@ -1056,23 +1062,49 @@ "notificationAddSave": { "message": "Зберегти" }, - "loginSaveSuccessDetails": { - "message": "$USERNAME$ збережено до Bitwarden.", + "notificationViewAria": { + "message": "Переглянути $ITEMNAME$, відкривається у новому вікні", "placeholders": { - "username": { + "itemName": { "content": "$1" } }, - "description": "Shown to user after login is saved." + "description": "Aria label for the view button in notification bar confirmation message" }, - "loginUpdatedSuccessDetails": { - "message": "$USERNAME$ оновлено у Bitwarden.", + "notificationEditTooltip": { + "message": "Edit before saving", + "description": "Tooltip and Aria label for edit button on cipher item" + }, + "newNotification": { + "message": "Нове сповіщення" + }, + "labelWithNotification": { + "message": "$LABEL$: нове сповіщення", + "description": "Label for the notification with a new login suggestion.", "placeholders": { - "username": { + "label": { + "content": "$1", + "example": "Login" + } + } + }, + "loginSaveConfirmation": { + "message": "$ITEMNAME$ збережено до Bitwarden.", + "placeholders": { + "itemName": { "content": "$1" } }, - "description": "Shown to user after login is updated." + "description": "Shown to user after item is saved." + }, + "loginUpdatedConfirmation": { + "message": "$ITEMNAME$ оновлено в Bitwarden.", + "placeholders": { + "itemName": { + "content": "$1" + } + }, + "description": "Shown to user after item is updated." }, "saveAsNewLoginAction": { "message": "Зберегти як новий запис", @@ -1082,12 +1114,12 @@ "message": "Оновити запис", "description": "Button text for updating an existing login entry." }, - "saveLoginPrompt": { - "message": "Зберегти запис?", + "saveLogin": { + "message": "Save login", "description": "Prompt asking the user if they want to save their login details." }, - "updateLoginPrompt": { - "message": "Оновити наявний запис?", + "updateLogin": { + "message": "Update existing login", "description": "Prompt asking the user if they want to update an existing login entry." }, "loginSaveSuccess": { @@ -3006,7 +3038,7 @@ } }, "exportingIndividualVaultWithAttachmentsDescription": { - "message": "Будуть експортовані лише записи особистого сховища, включно з вкладеннями, пов'язані з $EMAIL$. Записи сховища організації не експортуватимуться.", + "message": "Будуть експортовані лише записи особистого сховища включно з вкладеннями, пов'язані з $EMAIL$. Записи сховища організації не експортуватимуться.", "placeholders": { "email": { "content": "$1", @@ -4928,8 +4960,8 @@ "message": "Пароль згенеровано повторно", "description": "Notification message for when a password has been regenerated" }, - "saveLoginToBitwarden": { - "message": "Зберегти запис у Bitwarden?", + "saveToBitwarden": { + "message": "Зберегти в Bitwarden", "description": "Confirmation message for saving a login to Bitwarden" }, "spaceCharacterDescriptor": { @@ -5169,6 +5201,12 @@ "changeAtRiskPassword": { "message": "Змінити ризикований пароль" }, + "settingsVaultOptions": { + "message": "Vault options" + }, + "emptyVaultDescription": { + "message": "The vault protects more than just your passwords. Store secure logins, IDs, cards and notes securely here." + }, "introCarouselLabel": { "message": "Вітаємо в Bitwarden" }, @@ -5195,5 +5233,50 @@ }, "secureDevicesBody": { "message": "Зберігайте скільки завгодно паролів на необмеженій кількості пристроїв, використовуючи Bitwarden для мобільних пристроїв, браузерів та комп'ютерів." + }, + "emptyVaultNudgeTitle": { + "message": "Import existing passwords" + }, + "emptyVaultNudgeBody": { + "message": "Use the importer to quickly transfer logins to Bitwarden without manually adding them." + }, + "emptyVaultNudgeButton": { + "message": "Import now" + }, + "hasItemsVaultNudgeTitle": { + "message": "Welcome to your vault!" + }, + "hasItemsVaultNudgeBody": { + "message": "Autofill items for the current page\nFavorite items for easy access\nSearch your vault for something else" + }, + "newLoginNudgeTitle": { + "message": "Save time with autofill" + }, + "newLoginNudgeBody": { + "message": "Include a Website so this login appears as an autofill suggestion." + }, + "newCardNudgeTitle": { + "message": "Seamless online checkout" + }, + "newCardNudgeBody": { + "message": "With cards, easily autofill payment forms securely and accurately." + }, + "newIdentityNudgeTitle": { + "message": "Simplify creating accounts" + }, + "newIdentityNudgeBody": { + "message": "With identities, quickly autofill long registration or contact forms." + }, + "newNoteNudgeTitle": { + "message": "Keep your sensitive data safe" + }, + "newNoteNudgeBody": { + "message": "With notes, securely store sensitive data like banking or insurance details." + }, + "newSshNudgeTitle": { + "message": "Developer-friendly SSH access" + }, + "newSshNudgeBody": { + "message": "Store your keys and connect with the SSH agent for fast, encrypted authentication." } } diff --git a/apps/browser/src/_locales/vi/messages.json b/apps/browser/src/_locales/vi/messages.json index c6c6ed7ee6d..280a590033a 100644 --- a/apps/browser/src/_locales/vi/messages.json +++ b/apps/browser/src/_locales/vi/messages.json @@ -2,6 +2,9 @@ "appName": { "message": "Bitwarden" }, + "appLogoLabel": { + "message": "Bitwarden logo" + }, "extName": { "message": "Trình quản lý mật khẩu Bitwarden", "description": "Extension name, MUST be less than 40 characters (Safari restriction)" @@ -883,6 +886,9 @@ "followTheStepsBelowToFinishLoggingIn": { "message": "Follow the steps below to finish logging in." }, + "followTheStepsBelowToFinishLoggingInWithSecurityKey": { + "message": "Follow the steps below to finish logging in with your security key." + }, "restartRegistration": { "message": "Tiến hành đăng ký lại" }, @@ -1056,23 +1062,49 @@ "notificationAddSave": { "message": "Lưu" }, - "loginSaveSuccessDetails": { - "message": "$USERNAME$ saved to Bitwarden.", + "notificationViewAria": { + "message": "View $ITEMNAME$, opens in new window", "placeholders": { - "username": { + "itemName": { "content": "$1" } }, - "description": "Shown to user after login is saved." + "description": "Aria label for the view button in notification bar confirmation message" }, - "loginUpdatedSuccessDetails": { - "message": "$USERNAME$ updated in Bitwarden.", + "notificationEditTooltip": { + "message": "Edit before saving", + "description": "Tooltip and Aria label for edit button on cipher item" + }, + "newNotification": { + "message": "New notification" + }, + "labelWithNotification": { + "message": "$LABEL$: New notification", + "description": "Label for the notification with a new login suggestion.", "placeholders": { - "username": { + "label": { + "content": "$1", + "example": "Login" + } + } + }, + "loginSaveConfirmation": { + "message": "$ITEMNAME$ saved to Bitwarden.", + "placeholders": { + "itemName": { "content": "$1" } }, - "description": "Shown to user after login is updated." + "description": "Shown to user after item is saved." + }, + "loginUpdatedConfirmation": { + "message": "$ITEMNAME$ updated in Bitwarden.", + "placeholders": { + "itemName": { + "content": "$1" + } + }, + "description": "Shown to user after item is updated." }, "saveAsNewLoginAction": { "message": "Save as new login", @@ -1082,12 +1114,12 @@ "message": "Update login", "description": "Button text for updating an existing login entry." }, - "saveLoginPrompt": { - "message": "Save login?", + "saveLogin": { + "message": "Save login", "description": "Prompt asking the user if they want to save their login details." }, - "updateLoginPrompt": { - "message": "Update existing login?", + "updateLogin": { + "message": "Update existing login", "description": "Prompt asking the user if they want to update an existing login entry." }, "loginSaveSuccess": { @@ -4928,8 +4960,8 @@ "message": "Password regenerated", "description": "Notification message for when a password has been regenerated" }, - "saveLoginToBitwarden": { - "message": "Save login to Bitwarden?", + "saveToBitwarden": { + "message": "Save to Bitwarden", "description": "Confirmation message for saving a login to Bitwarden" }, "spaceCharacterDescriptor": { @@ -5169,6 +5201,12 @@ "changeAtRiskPassword": { "message": "Change at-risk password" }, + "settingsVaultOptions": { + "message": "Vault options" + }, + "emptyVaultDescription": { + "message": "The vault protects more than just your passwords. Store secure logins, IDs, cards and notes securely here." + }, "introCarouselLabel": { "message": "Welcome to Bitwarden" }, @@ -5195,5 +5233,50 @@ }, "secureDevicesBody": { "message": "Save unlimited passwords across unlimited devices with Bitwarden mobile, browser, and desktop apps." + }, + "emptyVaultNudgeTitle": { + "message": "Import existing passwords" + }, + "emptyVaultNudgeBody": { + "message": "Use the importer to quickly transfer logins to Bitwarden without manually adding them." + }, + "emptyVaultNudgeButton": { + "message": "Import now" + }, + "hasItemsVaultNudgeTitle": { + "message": "Welcome to your vault!" + }, + "hasItemsVaultNudgeBody": { + "message": "Autofill items for the current page\nFavorite items for easy access\nSearch your vault for something else" + }, + "newLoginNudgeTitle": { + "message": "Save time with autofill" + }, + "newLoginNudgeBody": { + "message": "Include a Website so this login appears as an autofill suggestion." + }, + "newCardNudgeTitle": { + "message": "Seamless online checkout" + }, + "newCardNudgeBody": { + "message": "With cards, easily autofill payment forms securely and accurately." + }, + "newIdentityNudgeTitle": { + "message": "Simplify creating accounts" + }, + "newIdentityNudgeBody": { + "message": "With identities, quickly autofill long registration or contact forms." + }, + "newNoteNudgeTitle": { + "message": "Keep your sensitive data safe" + }, + "newNoteNudgeBody": { + "message": "With notes, securely store sensitive data like banking or insurance details." + }, + "newSshNudgeTitle": { + "message": "Developer-friendly SSH access" + }, + "newSshNudgeBody": { + "message": "Store your keys and connect with the SSH agent for fast, encrypted authentication." } } diff --git a/apps/browser/src/_locales/zh_CN/messages.json b/apps/browser/src/_locales/zh_CN/messages.json index 662284568cd..521c72a2fb5 100644 --- a/apps/browser/src/_locales/zh_CN/messages.json +++ b/apps/browser/src/_locales/zh_CN/messages.json @@ -2,6 +2,9 @@ "appName": { "message": "Bitwarden" }, + "appLogoLabel": { + "message": "Bitwarden logo" + }, "extName": { "message": "Bitwarden 密码管理器", "description": "Extension name, MUST be less than 40 characters (Safari restriction)" @@ -842,10 +845,10 @@ "message": "无缝两步验证" }, "totpHelper": { - "message": "Bitwarden 可以存储并填充两步验证码。复制并粘贴密钥到此字段。" + "message": "Bitwarden 可以存储并填充两步验证码。将密钥复制并粘贴到此字段。" }, "totpHelperWithCapture": { - "message": "Bitwarden 可以存储并填充两步验证码。选择相机图标来截取此网站的验证器二维码,或者手动复制并粘贴密钥到此字段。" + "message": "Bitwarden 可以存储并填充两步验证码。选择相机图标来拍摄此网站的验证器二维码,或将密钥复制并粘贴到此字段。" }, "learnMoreAboutAuthenticators": { "message": "进一步了解验证器" @@ -883,6 +886,9 @@ "followTheStepsBelowToFinishLoggingIn": { "message": "按照以下步骤完成登录。" }, + "followTheStepsBelowToFinishLoggingInWithSecurityKey": { + "message": "请按照下面的步骤,使用您的安全密钥完成登录。" + }, "restartRegistration": { "message": "重启注册" }, @@ -1056,23 +1062,49 @@ "notificationAddSave": { "message": "保存" }, - "loginSaveSuccessDetails": { - "message": "$USERNAME$ 已保存到 Bitwarden。", + "notificationViewAria": { + "message": "在新窗口中查看 $ITEMNAME$", "placeholders": { - "username": { + "itemName": { "content": "$1" } }, - "description": "Shown to user after login is saved." + "description": "Aria label for the view button in notification bar confirmation message" }, - "loginUpdatedSuccessDetails": { - "message": "$USERNAME$ 已在 Bitwarden 中更新。", + "notificationEditTooltip": { + "message": "保存前编辑", + "description": "Tooltip and Aria label for edit button on cipher item" + }, + "newNotification": { + "message": "新通知" + }, + "labelWithNotification": { + "message": "$LABEL$:新通知", + "description": "Label for the notification with a new login suggestion.", "placeholders": { - "username": { + "label": { + "content": "$1", + "example": "Login" + } + } + }, + "loginSaveConfirmation": { + "message": "$ITEMNAME$ 已保存到 Bitwarden。", + "placeholders": { + "itemName": { "content": "$1" } }, - "description": "Shown to user after login is updated." + "description": "Shown to user after item is saved." + }, + "loginUpdatedConfirmation": { + "message": "$ITEMNAME$ 已在 Bitwarden 中更新。", + "placeholders": { + "itemName": { + "content": "$1" + } + }, + "description": "Shown to user after item is updated." }, "saveAsNewLoginAction": { "message": "保存为新的登录", @@ -1082,12 +1114,12 @@ "message": "更新登录", "description": "Button text for updating an existing login entry." }, - "saveLoginPrompt": { - "message": "保存登录吗?", + "saveLogin": { + "message": "保存登录", "description": "Prompt asking the user if they want to save their login details." }, - "updateLoginPrompt": { - "message": "更新现有的登录吗?", + "updateLogin": { + "message": "更新现有的登录", "description": "Prompt asking the user if they want to update an existing login entry." }, "loginSaveSuccess": { @@ -2148,7 +2180,7 @@ "message": "使用此用户名" }, "securePasswordGenerated": { - "message": "安全密码生成好了!别忘了也在网站上更新一下您的密码。" + "message": "安全的密码已生成!不要忘记在网站上更新您的密码。" }, "useGeneratorHelpTextPartOne": { "message": "使用生成器", @@ -4418,13 +4450,13 @@ "message": "项目历史记录" }, "lastEdited": { - "message": "上次编辑" + "message": "最后编辑于" }, "ownerYou": { "message": "所有者:您" }, "linked": { - "message": "链接型" + "message": "已链接" }, "copySuccessful": { "message": "复制成功" @@ -4928,8 +4960,8 @@ "message": "密码已重新生成", "description": "Notification message for when a password has been regenerated" }, - "saveLoginToBitwarden": { - "message": "将登录保存到 Bitwarden 吗?", + "saveToBitwarden": { + "message": "保存到 Bitwarden", "description": "Confirmation message for saving a login to Bitwarden" }, "spaceCharacterDescriptor": { @@ -5169,6 +5201,12 @@ "changeAtRiskPassword": { "message": "更改有风险的密码" }, + "settingsVaultOptions": { + "message": "密码库选项" + }, + "emptyVaultDescription": { + "message": "密码库不仅保护您的密码。在这里还可以安全地存储登录、ID、支付卡和笔记。" + }, "introCarouselLabel": { "message": "欢迎使用 Bitwarden" }, @@ -5195,5 +5233,50 @@ }, "secureDevicesBody": { "message": "使用 Bitwarden 移动端、浏览器和桌面 App 在无限制的设备上保存无限数量的密码。" + }, + "emptyVaultNudgeTitle": { + "message": "导入现有密码" + }, + "emptyVaultNudgeBody": { + "message": "使用导入器快速将登录传输到 Bitwarden 而无需手动添加。" + }, + "emptyVaultNudgeButton": { + "message": "立即导入" + }, + "hasItemsVaultNudgeTitle": { + "message": "欢迎来到您的密码库!" + }, + "hasItemsVaultNudgeBody": { + "message": "自动填充项目用于当前页面\n收藏夹项目用于轻松访问\n搜索密码库用于其他目的" + }, + "newLoginNudgeTitle": { + "message": "Save time with autofill" + }, + "newLoginNudgeBody": { + "message": "Include a Website so this login appears as an autofill suggestion." + }, + "newCardNudgeTitle": { + "message": "Seamless online checkout" + }, + "newCardNudgeBody": { + "message": "With cards, easily autofill payment forms securely and accurately." + }, + "newIdentityNudgeTitle": { + "message": "Simplify creating accounts" + }, + "newIdentityNudgeBody": { + "message": "With identities, quickly autofill long registration or contact forms." + }, + "newNoteNudgeTitle": { + "message": "Keep your sensitive data safe" + }, + "newNoteNudgeBody": { + "message": "With notes, securely store sensitive data like banking or insurance details." + }, + "newSshNudgeTitle": { + "message": "Developer-friendly SSH access" + }, + "newSshNudgeBody": { + "message": "Store your keys and connect with the SSH agent for fast, encrypted authentication." } } diff --git a/apps/browser/src/_locales/zh_TW/messages.json b/apps/browser/src/_locales/zh_TW/messages.json index 7d56ecd8e63..d122f7abab2 100644 --- a/apps/browser/src/_locales/zh_TW/messages.json +++ b/apps/browser/src/_locales/zh_TW/messages.json @@ -2,6 +2,9 @@ "appName": { "message": "Bitwarden" }, + "appLogoLabel": { + "message": "Bitwarden logo" + }, "extName": { "message": "Bitwarden - 免費密碼管理工具", "description": "Extension name, MUST be less than 40 characters (Safari restriction)" @@ -883,6 +886,9 @@ "followTheStepsBelowToFinishLoggingIn": { "message": "Follow the steps below to finish logging in." }, + "followTheStepsBelowToFinishLoggingInWithSecurityKey": { + "message": "Follow the steps below to finish logging in with your security key." + }, "restartRegistration": { "message": "重新啟動註冊" }, @@ -1056,23 +1062,49 @@ "notificationAddSave": { "message": "儲存" }, - "loginSaveSuccessDetails": { - "message": "$USERNAME$ saved to Bitwarden.", + "notificationViewAria": { + "message": "View $ITEMNAME$, opens in new window", "placeholders": { - "username": { + "itemName": { "content": "$1" } }, - "description": "Shown to user after login is saved." + "description": "Aria label for the view button in notification bar confirmation message" }, - "loginUpdatedSuccessDetails": { - "message": "$USERNAME$ updated in Bitwarden.", + "notificationEditTooltip": { + "message": "Edit before saving", + "description": "Tooltip and Aria label for edit button on cipher item" + }, + "newNotification": { + "message": "New notification" + }, + "labelWithNotification": { + "message": "$LABEL$: New notification", + "description": "Label for the notification with a new login suggestion.", "placeholders": { - "username": { + "label": { + "content": "$1", + "example": "Login" + } + } + }, + "loginSaveConfirmation": { + "message": "$ITEMNAME$ saved to Bitwarden.", + "placeholders": { + "itemName": { "content": "$1" } }, - "description": "Shown to user after login is updated." + "description": "Shown to user after item is saved." + }, + "loginUpdatedConfirmation": { + "message": "$ITEMNAME$ updated in Bitwarden.", + "placeholders": { + "itemName": { + "content": "$1" + } + }, + "description": "Shown to user after item is updated." }, "saveAsNewLoginAction": { "message": "Save as new login", @@ -1082,12 +1114,12 @@ "message": "Update login", "description": "Button text for updating an existing login entry." }, - "saveLoginPrompt": { - "message": "Save login?", + "saveLogin": { + "message": "Save login", "description": "Prompt asking the user if they want to save their login details." }, - "updateLoginPrompt": { - "message": "Update existing login?", + "updateLogin": { + "message": "Update existing login", "description": "Prompt asking the user if they want to update an existing login entry." }, "loginSaveSuccess": { @@ -4928,8 +4960,8 @@ "message": "密碼已重新產生", "description": "Notification message for when a password has been regenerated" }, - "saveLoginToBitwarden": { - "message": "在 Bitwarden 中儲存登入資訊?", + "saveToBitwarden": { + "message": "Save to Bitwarden", "description": "Confirmation message for saving a login to Bitwarden" }, "spaceCharacterDescriptor": { @@ -5169,6 +5201,12 @@ "changeAtRiskPassword": { "message": "Change at-risk password" }, + "settingsVaultOptions": { + "message": "Vault options" + }, + "emptyVaultDescription": { + "message": "The vault protects more than just your passwords. Store secure logins, IDs, cards and notes securely here." + }, "introCarouselLabel": { "message": "Welcome to Bitwarden" }, @@ -5195,5 +5233,50 @@ }, "secureDevicesBody": { "message": "Save unlimited passwords across unlimited devices with Bitwarden mobile, browser, and desktop apps." + }, + "emptyVaultNudgeTitle": { + "message": "Import existing passwords" + }, + "emptyVaultNudgeBody": { + "message": "Use the importer to quickly transfer logins to Bitwarden without manually adding them." + }, + "emptyVaultNudgeButton": { + "message": "Import now" + }, + "hasItemsVaultNudgeTitle": { + "message": "Welcome to your vault!" + }, + "hasItemsVaultNudgeBody": { + "message": "Autofill items for the current page\nFavorite items for easy access\nSearch your vault for something else" + }, + "newLoginNudgeTitle": { + "message": "Save time with autofill" + }, + "newLoginNudgeBody": { + "message": "Include a Website so this login appears as an autofill suggestion." + }, + "newCardNudgeTitle": { + "message": "Seamless online checkout" + }, + "newCardNudgeBody": { + "message": "With cards, easily autofill payment forms securely and accurately." + }, + "newIdentityNudgeTitle": { + "message": "Simplify creating accounts" + }, + "newIdentityNudgeBody": { + "message": "With identities, quickly autofill long registration or contact forms." + }, + "newNoteNudgeTitle": { + "message": "Keep your sensitive data safe" + }, + "newNoteNudgeBody": { + "message": "With notes, securely store sensitive data like banking or insurance details." + }, + "newSshNudgeTitle": { + "message": "Developer-friendly SSH access" + }, + "newSshNudgeBody": { + "message": "Store your keys and connect with the SSH agent for fast, encrypted authentication." } } 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 b8252aa6e13..ebf79af644c 100644 --- a/apps/browser/src/auth/popup/settings/account-security.component.html +++ b/apps/browser/src/auth/popup/settings/account-security.component.html @@ -23,7 +23,7 @@ = of(true); form = this.formBuilder.group({ @@ -147,11 +145,6 @@ export class AccountSecurityComponent implements OnInit, OnDestroy { ) {} async ngOnInit() { - // Firefox popup closes when unfocused by biometrics, blocking all unlock methods - if (this.platformUtilsService.getDevice() === DeviceType.FirefoxExtension) { - this.showAutoPrompt = false; - } - const hasMasterPassword = await this.userVerificationService.hasMasterPassword(); this.showMasterPasswordOnClientRestartOption = hasMasterPassword; const maximumVaultTimeoutPolicy = this.accountService.activeAccount$.pipe( diff --git a/apps/browser/src/auth/services/extension-two-factor-auth-email-component.service.spec.ts b/apps/browser/src/auth/services/extension-two-factor-auth-email-component.service.spec.ts index 223375fd903..01a0129d0e5 100644 --- a/apps/browser/src/auth/services/extension-two-factor-auth-email-component.service.spec.ts +++ b/apps/browser/src/auth/services/extension-two-factor-auth-email-component.service.spec.ts @@ -1,5 +1,6 @@ import { MockProxy, mock } from "jest-mock-extended"; +import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; import { DialogService } from "@bitwarden/components"; // Must mock modules before importing @@ -26,22 +27,26 @@ describe("ExtensionTwoFactorAuthEmailComponentService", () => { let dialogService: MockProxy; let window: MockProxy; + let configService: MockProxy; beforeEach(() => { jest.clearAllMocks(); dialogService = mock(); window = mock(); + configService = mock(); extensionTwoFactorAuthEmailComponentService = new ExtensionTwoFactorAuthEmailComponentService( dialogService, window, + configService, ); }); describe("openPopoutIfApprovedForEmail2fa", () => { it("should open a popout if the user confirms the warning to popout the extension when in the popup", async () => { // Arrange + configService.getFeatureFlag.mockResolvedValue(false); dialogService.openSimpleDialog.mockResolvedValue(true); jest.spyOn(BrowserPopupUtils, "inPopup").mockReturnValue(true); @@ -61,6 +66,7 @@ describe("ExtensionTwoFactorAuthEmailComponentService", () => { it("should not open a popout if the user cancels the warning to popout the extension when in the popup", async () => { // Arrange + configService.getFeatureFlag.mockResolvedValue(false); dialogService.openSimpleDialog.mockResolvedValue(false); jest.spyOn(BrowserPopupUtils, "inPopup").mockReturnValue(true); @@ -80,6 +86,7 @@ describe("ExtensionTwoFactorAuthEmailComponentService", () => { it("should not open a popout if not in the popup", async () => { // Arrange + configService.getFeatureFlag.mockResolvedValue(false); jest.spyOn(BrowserPopupUtils, "inPopup").mockReturnValue(false); // Act @@ -89,5 +96,15 @@ describe("ExtensionTwoFactorAuthEmailComponentService", () => { expect(dialogService.openSimpleDialog).not.toHaveBeenCalled(); expect(openTwoFactorAuthEmailPopout).not.toHaveBeenCalled(); }); + + it("does not prompt or open a popout if the feature flag is enabled", async () => { + configService.getFeatureFlag.mockResolvedValue(true); + jest.spyOn(BrowserPopupUtils, "inPopup").mockReturnValue(true); + + await extensionTwoFactorAuthEmailComponentService.openPopoutIfApprovedForEmail2fa(); + + expect(dialogService.openSimpleDialog).not.toHaveBeenCalled(); + expect(openTwoFactorAuthEmailPopout).not.toHaveBeenCalled(); + }); }); }); diff --git a/apps/browser/src/auth/services/extension-two-factor-auth-email-component.service.ts b/apps/browser/src/auth/services/extension-two-factor-auth-email-component.service.ts index 5d8d269412e..293d88c4e64 100644 --- a/apps/browser/src/auth/services/extension-two-factor-auth-email-component.service.ts +++ b/apps/browser/src/auth/services/extension-two-factor-auth-email-component.service.ts @@ -2,6 +2,8 @@ import { DefaultTwoFactorAuthEmailComponentService, TwoFactorAuthEmailComponentService, } from "@bitwarden/auth/angular"; +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 { openTwoFactorAuthEmailPopout } from "../../auth/popup/utils/auth-popout-window"; @@ -15,11 +17,21 @@ export class ExtensionTwoFactorAuthEmailComponentService constructor( private dialogService: DialogService, private window: Window, + private configService: ConfigService, ) { super(); } async openPopoutIfApprovedForEmail2fa(): Promise { + const isTwoFactorFormPersistenceEnabled = await this.configService.getFeatureFlag( + FeatureFlag.PM9115_TwoFactorExtensionDataPersistence, + ); + + if (isTwoFactorFormPersistenceEnabled) { + // If the feature flag is enabled, we don't need to prompt the user to open the popout + return; + } + if (BrowserPopupUtils.inPopup(this.window)) { const confirmed = await this.dialogService.openSimpleDialog({ title: { key: "warning" }, diff --git a/apps/browser/src/autofill/background/abstractions/notification.background.ts b/apps/browser/src/autofill/background/abstractions/notification.background.ts index 6b3c91a109c..db110319d20 100644 --- a/apps/browser/src/autofill/background/abstractions/notification.background.ts +++ b/apps/browser/src/autofill/background/abstractions/notification.background.ts @@ -2,6 +2,7 @@ import { NeverDomains } from "@bitwarden/common/models/domain/domain-service"; import { ServerConfig } from "@bitwarden/common/platform/abstractions/config/server-config"; import { FolderView } from "@bitwarden/common/vault/models/view/folder.view"; +import { CollectionView } from "../../content/components/common-types"; import { NotificationQueueMessageTypes } from "../../enums/notification-queue-message-type.enum"; import AutofillPageDetails from "../../models/autofill-page-details"; @@ -83,6 +84,7 @@ type NotificationBackgroundExtensionMessage = { tab?: chrome.tabs.Tab; sender?: string; notificationType?: string; + organizationId?: string; fadeOutNotification?: boolean; }; @@ -94,6 +96,10 @@ type NotificationBackgroundExtensionMessageHandlers = { [key: string]: CallableFunction; unlockCompleted: ({ message, sender }: BackgroundOnMessageHandlerParams) => Promise; bgGetFolderData: ({ message, sender }: BackgroundOnMessageHandlerParams) => Promise; + bgGetCollectionData: ({ + message, + sender, + }: BackgroundOnMessageHandlerParams) => Promise; bgCloseNotificationBar: ({ message, sender }: BackgroundOnMessageHandlerParams) => Promise; bgOpenAtRisksPasswords: ({ message, sender }: BackgroundOnMessageHandlerParams) => Promise; bgAdjustNotificationBar: ({ message, sender }: BackgroundOnMessageHandlerParams) => Promise; @@ -101,7 +107,14 @@ type NotificationBackgroundExtensionMessageHandlers = { bgChangedPassword: ({ message, sender }: BackgroundOnMessageHandlerParams) => Promise; bgRemoveTabFromNotificationQueue: ({ sender }: BackgroundSenderParam) => void; bgSaveCipher: ({ message, sender }: BackgroundOnMessageHandlerParams) => void; - bgOpenVault: ({ message, sender }: BackgroundOnMessageHandlerParams) => Promise; + bgOpenAddEditVaultItemPopout: ({ + message, + sender, + }: BackgroundOnMessageHandlerParams) => Promise; + bgOpenViewVaultItemPopout: ({ + message, + sender, + }: BackgroundOnMessageHandlerParams) => Promise; bgNeverSave: ({ sender }: BackgroundSenderParam) => Promise; bgUnlockPopoutOpened: ({ message, sender }: BackgroundOnMessageHandlerParams) => Promise; bgReopenUnlockPopout: ({ sender }: BackgroundSenderParam) => Promise; diff --git a/apps/browser/src/autofill/background/notification.background.spec.ts b/apps/browser/src/autofill/background/notification.background.spec.ts index ffc416ab62a..63ae1193737 100644 --- a/apps/browser/src/autofill/background/notification.background.spec.ts +++ b/apps/browser/src/autofill/background/notification.background.spec.ts @@ -1,6 +1,7 @@ import { mock, MockProxy } from "jest-mock-extended"; import { BehaviorSubject, firstValueFrom, of } from "rxjs"; +import { CollectionService } from "@bitwarden/admin-console/common"; import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction"; import { DefaultPolicyService } from "@bitwarden/common/admin-console/services/policy/default-policy.service"; import { AccountInfo, AccountService } from "@bitwarden/common/auth/abstractions/account.service"; @@ -53,6 +54,7 @@ describe("NotificationBackground", () => { let notificationBackground: NotificationBackground; const autofillService = mock(); const cipherService = mock(); + const collectionService = mock(); let activeAccountStatusMock$: BehaviorSubject; let authService: MockProxy; const policyService = mock(); @@ -83,6 +85,7 @@ describe("NotificationBackground", () => { authService, autofillService, cipherService, + collectionService, configService, domainSettingsService, environmentService, @@ -823,6 +826,7 @@ describe("NotificationBackground", () => { notificationBackground["notificationQueue"] = [queueMessage]; const cipherView = mock({ id: "testId", + name: "testItemName", login: { username: "testUser" }, }); getDecryptedCipherByIdSpy.mockResolvedValueOnce(cipherView); @@ -844,8 +848,9 @@ describe("NotificationBackground", () => { sender.tab, "saveCipherAttemptCompleted", { - username: cipherView.login.username, + itemName: "testItemName", cipherId: cipherView.id, + task: undefined, }, ); }); @@ -899,7 +904,7 @@ describe("NotificationBackground", () => { const cipherView = mock({ id: mockCipherId, organizationId: mockOrgId, - login: { username: "testUser" }, + name: "Test Item", }); getDecryptedCipherByIdSpy.mockResolvedValueOnce(cipherView); @@ -921,11 +926,11 @@ describe("NotificationBackground", () => { "saveCipherAttemptCompleted", { cipherId: "testId", + itemName: "Test Item", task: { orgName: "Org Name, LLC", remainingTasksCount: 1, }, - username: "testUser", }, ); }); @@ -1074,6 +1079,7 @@ describe("NotificationBackground", () => { notificationBackground["notificationQueue"] = [queueMessage]; const cipherView = mock({ id: "testId", + name: "testName", login: { username: "test", password: "password" }, }); folderExistsSpy.mockResolvedValueOnce(false); @@ -1097,8 +1103,8 @@ describe("NotificationBackground", () => { sender.tab, "saveCipherAttemptCompleted", { - username: cipherView.login.username, cipherId: cipherView.id, + itemName: cipherView.name, }, ); expect(tabSendMessageSpy).toHaveBeenCalledWith(sender.tab, { command: "addedCipher" }); diff --git a/apps/browser/src/autofill/background/notification.background.ts b/apps/browser/src/autofill/background/notification.background.ts index 6589252d94b..339b033809d 100644 --- a/apps/browser/src/autofill/background/notification.background.ts +++ b/apps/browser/src/autofill/background/notification.background.ts @@ -1,7 +1,8 @@ // FIXME: Update this file to be type safe and remove this and next line // @ts-strict-ignore -import { firstValueFrom, switchMap, map } from "rxjs"; +import { firstValueFrom, switchMap, map, of } from "rxjs"; +import { CollectionService } from "@bitwarden/admin-console/common"; 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 { PolicyType } from "@bitwarden/common/admin-console/enums"; @@ -41,12 +42,16 @@ import { SecurityTask } from "@bitwarden/common/vault/tasks/models/security-task import { openUnlockPopout } from "../../auth/popup/utils/auth-popout-window"; import { BrowserApi } from "../../platform/browser/browser-api"; -import { openAddEditVaultItemPopout } from "../../vault/popup/utils/vault-popout-window"; +import { + openAddEditVaultItemPopout, + openViewVaultItemPopout, +} from "../../vault/popup/utils/vault-popout-window"; import { OrganizationCategory, OrganizationCategories, NotificationCipherData, } from "../content/components/cipher/types"; +import { CollectionView } from "../content/components/common-types"; import { NotificationQueueMessageType } from "../enums/notification-queue-message-type.enum"; import { AutofillService } from "../services/abstractions/autofill.service"; @@ -67,6 +72,7 @@ import { OverlayBackgroundExtensionMessage } from "./abstractions/overlay.backgr export default class NotificationBackground { private openUnlockPopout = openUnlockPopout; private openAddEditVaultItemPopout = openAddEditVaultItemPopout; + private openViewVaultItemPopout = openViewVaultItemPopout; private notificationQueue: NotificationQueueMessageItem[] = []; private allowedRetryCommands: Set = new Set([ ExtensionCommand.AutofillLogin, @@ -88,9 +94,12 @@ export default class NotificationBackground { bgGetEnableAddedLoginPrompt: () => this.getEnableAddedLoginPrompt(), bgGetExcludedDomains: () => this.getExcludedDomains(), bgGetFolderData: () => this.getFolderData(), + bgGetCollectionData: ({ message }) => this.getCollectionData(message), bgGetOrgData: () => this.getOrgData(), bgNeverSave: ({ sender }) => this.saveNever(sender.tab), - bgOpenVault: ({ message, sender }) => this.openVault(message, sender.tab), + bgOpenAddEditVaultItemPopout: ({ message, sender }) => + this.openAddEditVaultItem(message, sender.tab), + bgOpenViewVaultItemPopout: ({ message, sender }) => this.viewItem(message, sender.tab), bgRemoveTabFromNotificationQueue: ({ sender }) => this.removeTabFromNotificationQueue(sender.tab), bgReopenUnlockPopout: ({ sender }) => this.openUnlockPopout(sender.tab), @@ -109,6 +118,7 @@ export default class NotificationBackground { private authService: AuthService, private autofillService: AutofillService, private cipherService: CipherService, + private collectionService: CollectionService, private configService: ConfigService, private domainSettingsService: DomainSettingsService, private environmentService: EnvironmentService, @@ -155,43 +165,87 @@ export default class NotificationBackground { /** * - * Gets the current active tab and retrieves all decrypted ciphers - * for the tab's URL. It constructs and returns an array of `NotificationCipherData` objects. + * Gets the current active tab and retrieves the relevant decrypted cipher + * for the tab's URL. It constructs and returns an array of `NotificationCipherData` objects or a singular object. * If no active tab or URL is found, it returns an empty array. + * If new login, returns a preview of the cipher. * * @returns {Promise} */ async getNotificationCipherData(): Promise { - const [currentTab, showFavicons, env] = await Promise.all([ + const [currentTab, showFavicons, env, activeUserId] = await Promise.all([ BrowserApi.getTabFromCurrentWindow(), firstValueFrom(this.domainSettingsService.showFavicons$), firstValueFrom(this.environmentService.environment$), + firstValueFrom(this.accountService.activeAccount$.pipe(getOptionalUserId)), + ]); + + if (!currentTab?.url || !activeUserId) { + return []; + } + + const [decryptedCiphers, organizations] = await Promise.all([ + this.cipherService.getAllDecryptedForUrl(currentTab.url, activeUserId), + firstValueFrom(this.organizationService.organizations$(activeUserId)), ]); const iconsServerUrl = env.getIconsUrl(); - const activeUserId = await firstValueFrom( - this.accountService.activeAccount$.pipe(getOptionalUserId), + + const getOrganizationType = (orgId?: string) => + organizations.find((org) => org.id === orgId)?.productTierType; + + const cipherQueueMessage = this.notificationQueue.find( + (message): message is AddChangePasswordQueueMessage | AddLoginQueueMessage => + message.type === NotificationQueueMessageType.ChangePassword || + message.type === NotificationQueueMessageType.AddLogin, ); - const decryptedCiphers = await this.cipherService.getAllDecryptedForUrl( - currentTab?.url, - activeUserId, + if (cipherQueueMessage) { + const cipherView = + cipherQueueMessage.type === NotificationQueueMessageType.ChangePassword + ? await this.getDecryptedCipherById(cipherQueueMessage.cipherId, activeUserId) + : this.convertAddLoginQueueMessageToCipherView(cipherQueueMessage); + + const organizationType = getOrganizationType(cipherView.organizationId); + return [ + this.convertToNotificationCipherData( + cipherView, + iconsServerUrl, + showFavicons, + organizationType, + ), + ]; + } + + return decryptedCiphers.map((view) => + this.convertToNotificationCipherData( + view, + iconsServerUrl, + showFavicons, + getOrganizationType(view.organizationId), + ), ); + } - const organizations = await firstValueFrom( - this.organizationService.organizations$(activeUserId), - ); + /** + * Converts a CipherView and organization type into a NotificationCipherData object + * for use in the notification bar. + * + * @returns A NotificationCipherData object containing the relevant cipher information. + */ - return decryptedCiphers.map((view) => { - const { id, name, reprompt, favorite, login, organizationId } = view; + convertToNotificationCipherData( + view: CipherView, + iconsServerUrl: string, + showFavicons: boolean, + organizationType?: ProductTierType, + ): NotificationCipherData { + const { id, name, reprompt, favorite, login } = view; - const organizationType = organizationId - ? organizations.find((org) => org.id === organizationId)?.productTierType - : null; - - const organizationCategories: OrganizationCategory[] = []; + const organizationCategories: OrganizationCategory[] = []; + if (organizationType != null) { if ( [ProductTierType.Teams, ProductTierType.Enterprise, ProductTierType.TeamsStarter].includes( organizationType, @@ -199,23 +253,22 @@ export default class NotificationBackground { ) { organizationCategories.push(OrganizationCategories.business); } + if ([ProductTierType.Families, ProductTierType.Free].includes(organizationType)) { organizationCategories.push(OrganizationCategories.family); } + } - return { - id, - name, - type: CipherType.Login, - reprompt, - favorite, - ...(organizationCategories.length ? { organizationCategories } : {}), - icon: buildCipherIcon(iconsServerUrl, view, showFavicons), - login: login && { - username: login.username, - }, - }; - }); + return { + id, + name, + type: CipherType.Login, + reprompt, + favorite, + ...(organizationCategories.length ? { organizationCategories } : {}), + icon: buildCipherIcon(iconsServerUrl, view, showFavicons), + login: login && { username: login.username }, + }; } /** @@ -638,8 +691,8 @@ export default class NotificationBackground { try { await this.cipherService.createWithServer(cipher); await BrowserApi.tabSendMessageData(tab, "saveCipherAttemptCompleted", { - username: queueMessage?.username && String(queueMessage.username), - cipherId: cipher?.id && String(cipher.id), + itemName: newCipher?.name && String(newCipher?.name), + cipherId: cipher?.id && String(cipher?.id), }); await BrowserApi.tabSendMessage(tab, { command: "addedCipher" }); } catch (error) { @@ -701,7 +754,7 @@ export default class NotificationBackground { await this.cipherService.updateWithServer(cipher); await BrowserApi.tabSendMessageData(tab, "saveCipherAttemptCompleted", { - username: cipherView?.login?.username && String(cipherView.login.username), + itemName: cipherView?.name && String(cipherView?.name), cipherId: cipherView?.id && String(cipherView.id), task: taskData, }); @@ -741,17 +794,52 @@ export default class NotificationBackground { userId, ); - await this.openAddEditVaultItemPopout(senderTab, { cipherId: cipherView.id }); + await this.openAddEditVaultItemPopout(senderTab, { cipherId: cipherView?.id }); } - private async openVault( + private async openAddEditVaultItem( message: NotificationBackgroundExtensionMessage, senderTab: chrome.tabs.Tab, ) { - if (!message.cipherId) { - await this.openAddEditVaultItemPopout(senderTab); + const { cipherId, organizationId, folder } = message; + const userId = await firstValueFrom(this.accountService.activeAccount$.pipe(getOptionalUserId)); + if (cipherId) { + await this.openAddEditVaultItemPopout(senderTab, { cipherId }); + return; } - await this.openAddEditVaultItemPopout(senderTab, { cipherId: message.cipherId }); + + const queueItem = this.notificationQueue.find((item) => item.tab.id === senderTab.id); + + if (queueItem?.type === NotificationQueueMessageType.AddLogin) { + const cipherView = this.convertAddLoginQueueMessageToCipherView(queueItem); + cipherView.organizationId = organizationId; + cipherView.folderId = folder; + + if (userId) { + await this.cipherService.setAddEditCipherInfo({ cipher: cipherView }, userId); + } + + await this.openAddEditVaultItemPopout(senderTab); + this.removeTabFromNotificationQueue(senderTab); + return; + } + + await this.openAddEditVaultItemPopout(senderTab); + } + + private async viewItem( + message: NotificationBackgroundExtensionMessage, + senderTab: chrome.tabs.Tab, + ) { + await Promise.all([ + this.openViewVaultItemPopout(senderTab, { + cipherId: message.cipherId, + action: null, + }), + BrowserApi.tabSendMessageData(senderTab, "closeNotificationBar", { + fadeOutNotification: !!message.fadeOutNotification, + }), + ]); } private async folderExists(folderId: string, userId: UserId) { @@ -780,7 +868,7 @@ export default class NotificationBackground { this.taskService.tasksEnabled$(userId).pipe( switchMap((tasksEnabled) => { if (!tasksEnabled) { - return []; + return of([]); } return this.taskService @@ -835,6 +923,25 @@ export default class NotificationBackground { return await firstValueFrom(this.folderService.folderViews$(activeUserId)); } + private async getCollectionData( + message: NotificationBackgroundExtensionMessage, + ): Promise { + const collections = (await this.collectionService.getAllDecrypted()).reduce( + (acc, collection) => { + if (collection.organizationId === message?.orgId) { + acc.push({ + id: collection.id, + name: collection.name, + organizationId: collection.organizationId, + }); + } + return acc; + }, + [], + ); + return collections; + } + private async getWebVaultUrl(): Promise { const env = await firstValueFrom(this.environmentService.environment$); return env.getWebVaultUrl(); @@ -861,6 +968,7 @@ export default class NotificationBackground { const organizations = await firstValueFrom( this.organizationService.organizations$(activeUserId), ); + return organizations.map((org) => { const { id, name, productTierType } = org; return { @@ -991,6 +1099,7 @@ export default class NotificationBackground { cipherView.folderId = folderId; cipherView.type = CipherType.Login; cipherView.login = loginView; + cipherView.organizationId = null; return cipherView; } diff --git a/apps/browser/src/autofill/background/overlay.background.ts b/apps/browser/src/autofill/background/overlay.background.ts index 4e2e773a0c7..ab5dd4abb8f 100644 --- a/apps/browser/src/autofill/background/overlay.background.ts +++ b/apps/browser/src/autofill/background/overlay.background.ts @@ -661,20 +661,23 @@ export class OverlayBackground implements OverlayBackgroundInterface { return this.inlineMenuFido2Credentials.has(credentialId); } + /** + * When focused field data contains account creation field type of totp + * and there are totp fields in the current frame for page details return true + * + * @returns boolean + */ private isTotpFieldForCurrentField(): boolean { if (!this.focusedFieldData) { return false; } - const { tabId, frameId } = this.focusedFieldData; - const pageDetailsMap = this.pageDetailsForTab[tabId]; - if (!pageDetailsMap || !pageDetailsMap.has(frameId)) { + const totpFields = this.getTotpFields(); + if (!totpFields) { return false; } - const pageDetail = pageDetailsMap.get(frameId); return ( - pageDetail?.details?.fields?.every((field) => - this.inlineMenuFieldQualificationService.isTotpField(field), - ) || false + totpFields.length > 0 && + this.focusedFieldData?.accountCreationFieldType === InlineMenuAccountCreationFieldType.Totp ); } @@ -1399,7 +1402,7 @@ export class OverlayBackground implements OverlayBackgroundInterface { const pageDetailsMap = this.pageDetailsForTab[currentTabId]; const pageDetails = pageDetailsMap?.get(currentFrameId); - const fields = pageDetails.details.fields; + const fields = pageDetails?.details?.fields || []; const totpFields = fields.filter((f) => this.inlineMenuFieldQualificationService.isTotpField(f), ); @@ -1679,7 +1682,12 @@ export class OverlayBackground implements OverlayBackgroundInterface { !this.focusedFieldMatchesFillType( focusedFieldData?.inlineMenuFillType, previousFocusedFieldData, - ) + ) || + // a TOTP field was just focused to - or unfocused from — a non-TOTP field + // may want to generalize this logic if cipher inline menu types exceed [general cipher, TOTP] + [focusedFieldData, previousFocusedFieldData].filter( + (fd) => fd?.accountCreationFieldType === InlineMenuAccountCreationFieldType.Totp, + ).length === 1 ) { const updateAllCipherTypes = !this.focusedFieldMatchesFillType( CipherType.Login, diff --git a/apps/browser/src/autofill/content/components/buttons/edit-button.ts b/apps/browser/src/autofill/content/components/buttons/edit-button.ts index 67221f5be18..a0037146db2 100644 --- a/apps/browser/src/autofill/content/components/buttons/edit-button.ts +++ b/apps/browser/src/autofill/content/components/buttons/edit-button.ts @@ -21,6 +21,7 @@ export function EditButton({ { if (!disabled) { diff --git a/apps/browser/src/autofill/content/components/cipher/cipher-action.ts b/apps/browser/src/autofill/content/components/cipher/cipher-action.ts index aaa4b11d8a2..388df5e58fe 100644 --- a/apps/browser/src/autofill/content/components/cipher/cipher-action.ts +++ b/apps/browser/src/autofill/content/components/cipher/cipher-action.ts @@ -8,24 +8,24 @@ export function CipherAction({ handleAction = () => { /* no-op */ }, + i18n, notificationType, theme, }: { handleAction?: (e: Event) => void; + i18n: { [key: string]: string }; notificationType: typeof NotificationTypes.Change | typeof NotificationTypes.Add; theme: Theme; }) { return notificationType === NotificationTypes.Change ? BadgeButton({ buttonAction: handleAction, - // @TODO localize - buttonText: "Update", + buttonText: i18n.notificationUpdate, theme, }) : EditButton({ buttonAction: handleAction, - // @TODO localize - buttonText: "Edit", + buttonText: i18n.notificationEditTooltip, theme, }); } diff --git a/apps/browser/src/autofill/content/components/cipher/cipher-info.ts b/apps/browser/src/autofill/content/components/cipher/cipher-info.ts index e3d237b9bc6..df3f2d7fa16 100644 --- a/apps/browser/src/autofill/content/components/cipher/cipher-info.ts +++ b/apps/browser/src/autofill/content/components/cipher/cipher-info.ts @@ -14,7 +14,7 @@ export function CipherInfo({ cipher, theme }: { cipher: NotificationCipherData; return html` - + ${[ name, hasIndicatorIcons diff --git a/apps/browser/src/autofill/content/components/cipher/cipher-item.ts b/apps/browser/src/autofill/content/components/cipher/cipher-item.ts index 96b44d2c0cc..8ab29860f3b 100644 --- a/apps/browser/src/autofill/content/components/cipher/cipher-item.ts +++ b/apps/browser/src/autofill/content/components/cipher/cipher-item.ts @@ -19,11 +19,13 @@ const cipherIconWidth = "24px"; export function CipherItem({ cipher, handleAction, + i18n, notificationType, theme = ThemeTypes.Light, }: { cipher: NotificationCipherData; handleAction?: (e: Event) => void; + i18n: { [key: string]: string }; notificationType?: NotificationType; theme: Theme; }) { @@ -34,7 +36,7 @@ export function CipherItem({ if (notificationType === NotificationTypes.Change || notificationType === NotificationTypes.Add) { cipherActionButton = html` - ${CipherAction({ handleAction, notificationType, theme })} + ${CipherAction({ handleAction, i18n, notificationType, theme })} `; } diff --git a/apps/browser/src/autofill/content/components/common-types.ts b/apps/browser/src/autofill/content/components/common-types.ts index df11e140d70..591c579bae5 100644 --- a/apps/browser/src/autofill/content/components/common-types.ts +++ b/apps/browser/src/autofill/content/components/common-types.ts @@ -26,3 +26,9 @@ export type OrgView = { name: string; productTierType?: ProductTierType; }; + +export type CollectionView = { + id: string; + name: string; + organizationId: string; +}; diff --git a/apps/browser/src/autofill/content/components/icons/pencil-square.ts b/apps/browser/src/autofill/content/components/icons/pencil-square.ts index 11366f2631a..c1b21fdc64e 100644 --- a/apps/browser/src/autofill/content/components/icons/pencil-square.ts +++ b/apps/browser/src/autofill/content/components/icons/pencil-square.ts @@ -8,7 +8,7 @@ export function PencilSquare({ color, disabled, theme }: IconProps) { const shapeColor = disabled ? themes[theme].secondary["300"] : color || themes[theme].text.main; return html` - + void; + i18n: { [key: string]: string }; notificationType: typeof NotificationTypes.Change | typeof NotificationTypes.Add; theme: Theme; }; diff --git a/apps/browser/src/autofill/content/components/lit-stories/notification/body.lit-stories.ts b/apps/browser/src/autofill/content/components/lit-stories/notification/body.lit-stories.ts index 32b4170d1da..13e2322a9f2 100644 --- a/apps/browser/src/autofill/content/components/lit-stories/notification/body.lit-stories.ts +++ b/apps/browser/src/autofill/content/components/lit-stories/notification/body.lit-stories.ts @@ -10,6 +10,7 @@ import { NotificationBody } from "../../notification/body"; type Args = { ciphers: NotificationCipherData[]; + i18n: { [key: string]: string }; notificationType: NotificationType; theme: Theme; handleEditOrUpdateAction: (e: Event) => void; diff --git a/apps/browser/src/autofill/content/components/notification/body.ts b/apps/browser/src/autofill/content/components/notification/body.ts index 66b580bde43..cc0fa359303 100644 --- a/apps/browser/src/autofill/content/components/notification/body.ts +++ b/apps/browser/src/autofill/content/components/notification/body.ts @@ -17,12 +17,14 @@ const { css } = createEmotion({ export function NotificationBody({ ciphers = [], + i18n, notificationType, theme = ThemeTypes.Light, handleEditOrUpdateAction, }: { ciphers?: NotificationCipherData[]; customClasses?: string[]; + i18n: { [key: string]: string }; notificationType?: NotificationType; theme: Theme; handleEditOrUpdateAction: (e: Event) => void; @@ -37,6 +39,7 @@ export function NotificationBody({ theme, children: CipherItem({ cipher, + i18n, notificationType, theme, handleAction: handleEditOrUpdateAction, diff --git a/apps/browser/src/autofill/content/components/notification/button-row.ts b/apps/browser/src/autofill/content/components/notification/button-row.ts index 8661f5957e1..f9cd9ed5e7a 100644 --- a/apps/browser/src/autofill/content/components/notification/button-row.ts +++ b/apps/browser/src/autofill/content/components/notification/button-row.ts @@ -3,9 +3,12 @@ import { html } from "lit"; import { ProductTierType } from "@bitwarden/common/billing/enums"; import { Theme } from "@bitwarden/common/platform/enums"; -import { Option, OrgView, FolderView } from "../common-types"; -import { Business, Family, Folder, User } from "../icons"; +import { Option, OrgView, FolderView, CollectionView } from "../common-types"; +import { Business, Family, Folder, User, CollectionShared } from "../icons"; import { ButtonRow } from "../rows/button-row"; +import { selectedCollection as selectedCollectionSignal } from "../signals/selected-collection"; +import { selectedFolder as selectedFolderSignal } from "../signals/selected-folder"; +import { selectedVault as selectedVaultSignal } from "../signals/selected-vault"; function getVaultIconByProductTier(productTierType?: ProductTierType): Option["icon"] { switch (productTierType) { @@ -21,44 +24,56 @@ function getVaultIconByProductTier(productTierType?: ProductTierType): Option["i } } +// Value represents default selector state outside of data-driven state +const defaultNoneSelectValue = "0"; + export type NotificationButtonRowProps = { - theme: Theme; + collections?: CollectionView[]; + folders?: FolderView[]; + i18n: { [key: string]: string }; + organizations?: OrgView[]; primaryButton: { text: string; handlePrimaryButtonClick: (args: any) => void; }; - folders?: FolderView[]; - organizations?: OrgView[]; + personalVaultIsAllowed: boolean; + theme: Theme; }; export function NotificationButtonRow({ + collections, folders, + i18n, organizations, primaryButton, + personalVaultIsAllowed, theme, }: NotificationButtonRowProps) { const currentUserVaultOption: Option = { icon: User, default: true, - text: "My vault", // @TODO localize - value: "0", + text: i18n.myVault, + value: defaultNoneSelectValue, }; - const organizationOptions: Option[] = organizations?.length - ? organizations.reduce( - (options, { id, name, productTierType }: OrgView) => { - const icon = getVaultIconByProductTier(productTierType); - return [ - ...options, - { - icon, - text: name, - value: id, - }, - ]; - }, - [currentUserVaultOption], - ) - : ([] as Option[]); + + // Do not include user vault if disallowed by org policy + const initialVaultOptions = [ + ...(personalVaultIsAllowed ? [currentUserVaultOption] : []), + ] as Option[]; + + const vaultOptions: Option[] = organizations?.length + ? organizations.reduce((options, { id, name, productTierType }: OrgView) => { + const icon = getVaultIconByProductTier(productTierType); + return [ + ...options, + { + icon, + text: name, + value: id, + }, + ]; + }, initialVaultOptions) + : initialVaultOptions; const folderOptions: Option[] = folders?.length ? folders.reduce( @@ -67,7 +82,7 @@ export function NotificationButtonRow({ { icon: Folder, text: name, - value: id === null ? "0" : id, + value: id === null ? defaultNoneSelectValue : id, default: id === null, }, ], @@ -75,26 +90,67 @@ export function NotificationButtonRow({ ) : []; + const collectionOptions: Option[] = collections?.length + ? collections.reduce( + (options, { id, name }: any) => [ + ...options, + { + icon: CollectionShared, + text: name, + value: id === null ? defaultNoneSelectValue : id, + default: id === null, + }, + ], + [], + ) + : []; + + if (vaultOptions.length === 1) { + selectedVaultSignal?.set(vaultOptions[0].value); + + // If the individual vault is disabled by a vault policy, + // a collection selection is required + if ( + !personalVaultIsAllowed && + collections?.length && + selectedCollectionSignal.get() === defaultNoneSelectValue + ) { + selectedCollectionSignal?.set(collections[0].id); + } + } + return html` ${ButtonRow({ theme, primaryButton, selectButtons: [ - ...(organizationOptions.length > 1 + ...(vaultOptions.length > 1 ? [ { id: "organization", - label: "Vault", // @TODO localize - options: organizationOptions, + label: i18n.vault, + options: vaultOptions, + selectedSignal: selectedVaultSignal, }, ] : []), - ...(folderOptions.length > 1 + ...(folderOptions.length > 1 && !collectionOptions.length ? [ { id: "folder", - label: "Folder", // @TODO localize + label: i18n.folder, options: folderOptions, + selectedSignal: selectedFolderSignal, + }, + ] + : []), + ...(collectionOptions.length > 1 + ? [ + { + id: "collection", + label: "Collection", // @TODO localize + options: collectionOptions, + selectedSignal: selectedCollectionSignal, }, ] : []), diff --git a/apps/browser/src/autofill/content/components/notification/confirmation/body.ts b/apps/browser/src/autofill/content/components/notification/confirmation/body.ts index d2ac7f36277..0508991c5da 100644 --- a/apps/browser/src/autofill/content/components/notification/confirmation/body.ts +++ b/apps/browser/src/autofill/content/components/notification/confirmation/body.ts @@ -16,16 +16,18 @@ const { css } = createEmotion({ export type NotificationConfirmationBodyProps = { buttonText: string; + itemName: string; confirmationMessage: string; error?: string; messageDetails?: string; tasksAreComplete?: boolean; theme: Theme; - handleOpenVault: (e: Event) => void; + handleOpenVault: () => void; }; export function NotificationConfirmationBody({ buttonText, + itemName, confirmationMessage, error, messageDetails, @@ -43,6 +45,7 @@ export function NotificationConfirmationBody({ ${showConfirmationMessage ? NotificationConfirmationMessage({ buttonText, + itemName, message: confirmationMessage, messageDetails, theme, diff --git a/apps/browser/src/autofill/content/components/notification/confirmation/container.ts b/apps/browser/src/autofill/content/components/notification/confirmation/container.ts index a071338af9a..5cc977cf4cb 100644 --- a/apps/browser/src/autofill/content/components/notification/confirmation/container.ts +++ b/apps/browser/src/autofill/content/components/notification/confirmation/container.ts @@ -20,14 +20,14 @@ import { NotificationConfirmationFooter } from "./footer"; export type NotificationConfirmationContainerProps = NotificationBarIframeInitData & { handleCloseNotification: (e: Event) => void; - handleOpenVault: (e: Event) => void; + handleOpenVault: () => void; handleOpenTasks: (e: Event) => void; } & { error?: string; i18n: { [key: string]: string }; + itemName: string; task?: NotificationTaskInfo; type: NotificationType; - username: string; }; export function NotificationConfirmationContainer({ @@ -36,13 +36,13 @@ export function NotificationConfirmationContainer({ handleOpenVault, handleOpenTasks, i18n, + itemName, task, theme = ThemeTypes.Light, type, - username, }: NotificationConfirmationContainerProps) { const headerMessage = getHeaderMessage(i18n, type, error); - const confirmationMessage = getConfirmationMessage(i18n, username, type, error); + const confirmationMessage = getConfirmationMessage(i18n, itemName, type, error); const buttonText = error ? i18n.newItem : i18n.view; let messageDetails: string | undefined; @@ -71,6 +71,7 @@ export function NotificationConfirmationContainer({ })} ${NotificationConfirmationBody({ buttonText, + itemName, confirmationMessage, tasksAreComplete, messageDetails, @@ -106,19 +107,17 @@ const notificationContainerStyles = (theme: Theme) => css` function getConfirmationMessage( i18n: { [key: string]: string }, - username: string, + itemName: string, type?: NotificationType, error?: string, ) { - const loginSaveSuccessDetails = chrome.i18n.getMessage("loginSaveSuccessDetails", [username]); - const loginUpdatedSuccessDetails = chrome.i18n.getMessage("loginUpdatedSuccessDetails", [ - username, - ]); + const loginSaveConfirmation = chrome.i18n.getMessage("loginSaveConfirmation", [itemName]); + const loginUpdatedConfirmation = chrome.i18n.getMessage("loginUpdatedConfirmation", [itemName]); if (error) { return i18n.saveFailureDetails; } - return type === "add" ? loginSaveSuccessDetails : loginUpdatedSuccessDetails; + return type === "add" ? loginSaveConfirmation : loginUpdatedConfirmation; } function getHeaderMessage( diff --git a/apps/browser/src/autofill/content/components/notification/confirmation/message.ts b/apps/browser/src/autofill/content/components/notification/confirmation/message.ts index c018371caff..3707e628370 100644 --- a/apps/browser/src/autofill/content/components/notification/confirmation/message.ts +++ b/apps/browser/src/autofill/content/components/notification/confirmation/message.ts @@ -7,19 +7,23 @@ import { themes, typography } from "../../constants/styles"; export type NotificationConfirmationMessageProps = { buttonText?: string; + itemName: string; message?: string; messageDetails?: string; - handleClick: (e: Event) => void; + handleClick: () => void; theme: Theme; }; export function NotificationConfirmationMessage({ buttonText, + itemName, message, messageDetails, handleClick, theme, }: NotificationConfirmationMessageProps) { + const buttonAria = chrome.i18n.getMessage("notificationViewAria", [itemName]); + return html` ${message || buttonText @@ -35,6 +39,10 @@ export function NotificationConfirmationMessage({ title=${buttonText} class=${notificationConfirmationButtonTextStyles(theme)} @click=${handleClick} + @keydown=${(e: KeyboardEvent) => handleButtonKeyDown(e, handleClick)} + aria-label=${buttonAria} + tabindex="0" + role="button" > ${buttonText} @@ -81,3 +89,10 @@ const AdditionalMessageStyles = ({ theme }: { theme: Theme }) => css` font-size: 14px; color: ${themes[theme].text.muted}; `; + +function handleButtonKeyDown(event: KeyboardEvent, handleClick: () => void) { + if (event.key === "Enter" || event.key === " ") { + event.preventDefault(); + handleClick(); + } +} diff --git a/apps/browser/src/autofill/content/components/notification/container.ts b/apps/browser/src/autofill/content/components/notification/container.ts index c29f58e116b..b21a05696c1 100644 --- a/apps/browser/src/autofill/content/components/notification/container.ts +++ b/apps/browser/src/autofill/content/components/notification/container.ts @@ -9,7 +9,7 @@ import { NotificationType, } from "../../../notification/abstractions/notification-bar"; import { NotificationCipherData } from "../cipher/types"; -import { FolderView, OrgView } from "../common-types"; +import { CollectionView, FolderView, OrgView } from "../common-types"; import { themes, spacing } from "../constants/styles"; import { NotificationBody, componentClassPrefix as notificationBodyClassPrefix } from "./body"; @@ -25,9 +25,11 @@ export type NotificationContainerProps = NotificationBarIframeInitData & { handleEditOrUpdateAction: (e: Event) => void; } & { ciphers?: NotificationCipherData[]; + collections?: CollectionView[]; folders?: FolderView[]; i18n: { [key: string]: string }; organizations?: OrgView[]; + personalVaultIsAllowed?: boolean; type: NotificationType; // @TODO typing override for generic `NotificationBarIframeInitData.type` }; @@ -36,9 +38,11 @@ export function NotificationContainer({ handleEditOrUpdateAction, handleSaveAction, ciphers, + collections, folders, i18n, organizations, + personalVaultIsAllowed = true, theme = ThemeTypes.Light, type, }: NotificationContainerProps) { @@ -59,14 +63,17 @@ export function NotificationContainer({ ciphers, notificationType: type, theme, + i18n, }) : null} ${NotificationFooter({ handleSaveAction, + collections, folders, i18n, notificationType: type, organizations, + personalVaultIsAllowed, theme, })} @@ -95,9 +102,9 @@ const notificationContainerStyles = (theme: Theme) => css` function getHeaderMessage(i18n: { [key: string]: string }, type?: NotificationType) { switch (type) { case NotificationTypes.Add: - return i18n.saveAsNewLoginAction; + return i18n.saveLogin; case NotificationTypes.Change: - return i18n.updateLoginPrompt; + return i18n.updateLogin; case NotificationTypes.Unlock: return ""; default: diff --git a/apps/browser/src/autofill/content/components/notification/footer.ts b/apps/browser/src/autofill/content/components/notification/footer.ts index 8ed69a96ad9..baa1a2ecffc 100644 --- a/apps/browser/src/autofill/content/components/notification/footer.ts +++ b/apps/browser/src/autofill/content/components/notification/footer.ts @@ -7,25 +7,29 @@ import { NotificationType, NotificationTypes, } from "../../../notification/abstractions/notification-bar"; -import { OrgView, FolderView } from "../common-types"; +import { OrgView, FolderView, CollectionView } from "../common-types"; import { spacing, themes } from "../constants/styles"; import { NotificationButtonRow } from "./button-row"; export type NotificationFooterProps = { + collections?: CollectionView[]; folders?: FolderView[]; i18n: { [key: string]: string }; notificationType?: NotificationType; organizations?: OrgView[]; + personalVaultIsAllowed: boolean; theme: Theme; handleSaveAction: (e: Event) => void; }; export function NotificationFooter({ + collections, folders, i18n, notificationType, organizations, + personalVaultIsAllowed, theme, handleSaveAction, }: NotificationFooterProps) { @@ -36,12 +40,15 @@ export function NotificationFooter({ ${!isChangeNotification ? NotificationButtonRow({ + collections, folders, organizations, + i18n, primaryButton: { handlePrimaryButtonClick: handleSaveAction, text: primaryButtonText, }, + personalVaultIsAllowed, theme, }) : nothing} diff --git a/apps/browser/src/autofill/content/components/option-selection/option-selection.ts b/apps/browser/src/autofill/content/components/option-selection/option-selection.ts index 5f43e7a0256..49b51852a39 100644 --- a/apps/browser/src/autofill/content/components/option-selection/option-selection.ts +++ b/apps/browser/src/autofill/content/components/option-selection/option-selection.ts @@ -32,6 +32,9 @@ export class OptionSelection extends LitElement { @property({ type: (selectedOption: Option["value"]) => selectedOption }) handleSelectionUpdate?: (args: any) => void; + @property({ attribute: false }) + selectedSignal?: { set: (value: any) => void }; + @state() private showMenu = false; @@ -77,7 +80,7 @@ export class OptionSelection extends LitElement { private handleOptionSelection = (selectedOption: Option) => { this.showMenu = false; this.selection = selectedOption; - + this.selectedSignal?.set(selectedOption.value); // Any side-effects that should occur from the selection this.handleSelectionUpdate?.(selectedOption.value); }; diff --git a/apps/browser/src/autofill/content/components/rows/button-row.ts b/apps/browser/src/autofill/content/components/rows/button-row.ts index 80dcd0de125..f6674da6b6e 100644 --- a/apps/browser/src/autofill/content/components/rows/button-row.ts +++ b/apps/browser/src/autofill/content/components/rows/button-row.ts @@ -19,6 +19,7 @@ export type ButtonRowProps = { label?: string; options: Option[]; handleSelectionUpdate?: (args: any) => void; + selectedSignal?: { set: (value: any) => void }; }[]; }; @@ -32,7 +33,7 @@ export function ButtonRow({ theme, primaryButton, selectButtons }: ButtonRowProp })} ${selectButtons?.map( - ({ id, label, options, handleSelectionUpdate }) => + ({ id, label, options, handleSelectionUpdate, selectedSignal }) => html` ` || nothing, )} diff --git a/apps/browser/src/autofill/content/components/signals/selected-collection.ts b/apps/browser/src/autofill/content/components/signals/selected-collection.ts new file mode 100644 index 00000000000..7e6a8d69e3a --- /dev/null +++ b/apps/browser/src/autofill/content/components/signals/selected-collection.ts @@ -0,0 +1,3 @@ +import { signal } from "@lit-labs/signals"; + +export const selectedCollection = signal("0"); diff --git a/apps/browser/src/autofill/content/components/signals/selected-folder.ts b/apps/browser/src/autofill/content/components/signals/selected-folder.ts new file mode 100644 index 00000000000..4c9e30521d5 --- /dev/null +++ b/apps/browser/src/autofill/content/components/signals/selected-folder.ts @@ -0,0 +1,3 @@ +import { signal } from "@lit-labs/signals"; + +export const selectedFolder = signal("0"); diff --git a/apps/browser/src/autofill/content/components/signals/selected-vault.ts b/apps/browser/src/autofill/content/components/signals/selected-vault.ts new file mode 100644 index 00000000000..d74549b1c43 --- /dev/null +++ b/apps/browser/src/autofill/content/components/signals/selected-vault.ts @@ -0,0 +1,3 @@ +import { signal } from "@lit-labs/signals"; + +export const selectedVault = signal("0"); diff --git a/apps/browser/src/autofill/enums/autofill-overlay.enum.ts b/apps/browser/src/autofill/enums/autofill-overlay.enum.ts index 66ad0da546d..9cc457f3c1a 100644 --- a/apps/browser/src/autofill/enums/autofill-overlay.enum.ts +++ b/apps/browser/src/autofill/enums/autofill-overlay.enum.ts @@ -32,6 +32,7 @@ export const InlineMenuAccountCreationFieldType = { Text: "text", Email: "email", Password: "password", + Totp: "totp", } as const; export type InlineMenuAccountCreationFieldTypes = diff --git a/apps/browser/src/autofill/notification/abstractions/notification-bar.ts b/apps/browser/src/autofill/notification/abstractions/notification-bar.ts index cbfeffcf2f4..8256190ea55 100644 --- a/apps/browser/src/autofill/notification/abstractions/notification-bar.ts +++ b/apps/browser/src/autofill/notification/abstractions/notification-bar.ts @@ -1,7 +1,11 @@ import { Theme } from "@bitwarden/common/platform/enums"; import { NotificationCipherData } from "../../../autofill/content/components/cipher/types"; -import { FolderView, OrgView } from "../../../autofill/content/components/common-types"; +import { + FolderView, + OrgView, + CollectionView, +} from "../../../autofill/content/components/common-types"; const NotificationTypes = { Add: "add", @@ -19,6 +23,7 @@ type NotificationTaskInfo = { type NotificationBarIframeInitData = { ciphers?: NotificationCipherData[]; folders?: FolderView[]; + collections?: CollectionView[]; importType?: string; isVaultLocked?: boolean; launchTimestamp?: number; @@ -33,7 +38,7 @@ type NotificationBarWindowMessage = { data?: { cipherId?: string; task?: NotificationTaskInfo; - username?: string; + itemName?: string; }; error?: string; initData?: NotificationBarIframeInitData; diff --git a/apps/browser/src/autofill/notification/bar.ts b/apps/browser/src/autofill/notification/bar.ts index d660790ee63..162912c5596 100644 --- a/apps/browser/src/autofill/notification/bar.ts +++ b/apps/browser/src/autofill/notification/bar.ts @@ -6,9 +6,11 @@ import type { FolderView } from "@bitwarden/common/vault/models/view/folder.view import { AdjustNotificationBarMessageData } from "../background/abstractions/notification.background"; import { NotificationCipherData } from "../content/components/cipher/types"; -import { OrgView } from "../content/components/common-types"; +import { CollectionView, OrgView } from "../content/components/common-types"; import { NotificationConfirmationContainer } from "../content/components/notification/confirmation/container"; import { NotificationContainer } from "../content/components/notification/container"; +import { selectedFolder as selectedFolderSignal } from "../content/components/signals/selected-folder"; +import { selectedVault as selectedVaultSignal } from "../content/components/signals/selected-vault"; import { buildSvgDomElement } from "../utils"; import { circleCheckIcon } from "../utils/svg-icons"; @@ -41,6 +43,7 @@ function load() { applyNotificationBarStyle(); }); } + function applyNotificationBarStyle() { if (!useComponentBar) { // eslint-disable-next-line @typescript-eslint/no-require-imports @@ -53,31 +56,36 @@ function getI18n() { return { appName: chrome.i18n.getMessage("appName"), close: chrome.i18n.getMessage("close"), + collection: chrome.i18n.getMessage("collection"), folder: chrome.i18n.getMessage("folder"), loginSaveSuccess: chrome.i18n.getMessage("loginSaveSuccess"), - loginSaveSuccessDetails: chrome.i18n.getMessage("loginSaveSuccessDetails"), + loginSaveConfirmation: chrome.i18n.getMessage("loginSaveConfirmation"), loginUpdateSuccess: chrome.i18n.getMessage("loginUpdateSuccess"), - loginUpdateSuccessDetails: chrome.i18n.getMessage("loginUpdatedSuccessDetails"), + loginUpdateConfirmation: chrome.i18n.getMessage("loginUpdatedConfirmation"), loginUpdateTaskSuccess: chrome.i18n.getMessage("loginUpdateTaskSuccess"), loginUpdateTaskSuccessAdditional: chrome.i18n.getMessage("loginUpdateTaskSuccessAdditional"), nextSecurityTaskAction: chrome.i18n.getMessage("nextSecurityTaskAction"), newItem: chrome.i18n.getMessage("newItem"), never: chrome.i18n.getMessage("never"), + myVault: chrome.i18n.getMessage("myVault"), notificationAddDesc: chrome.i18n.getMessage("notificationAddDesc"), notificationAddSave: chrome.i18n.getMessage("notificationAddSave"), notificationChangeDesc: chrome.i18n.getMessage("notificationChangeDesc"), - notificationChangeSave: chrome.i18n.getMessage("notificationChangeSave"), + notificationUpdate: chrome.i18n.getMessage("notificationChangeSave"), notificationEdit: chrome.i18n.getMessage("edit"), + notificationEditTooltip: chrome.i18n.getMessage("notificationEditTooltip"), notificationUnlock: chrome.i18n.getMessage("notificationUnlock"), notificationUnlockDesc: chrome.i18n.getMessage("notificationUnlockDesc"), + notificationViewAria: chrome.i18n.getMessage("notificationViewAria"), saveAction: chrome.i18n.getMessage("notificationAddSave"), saveAsNewLoginAction: chrome.i18n.getMessage("saveAsNewLoginAction"), saveFailure: chrome.i18n.getMessage("saveFailure"), saveFailureDetails: chrome.i18n.getMessage("saveFailureDetails"), - saveLoginPrompt: chrome.i18n.getMessage("saveLoginPrompt"), + saveLogin: chrome.i18n.getMessage("saveLogin"), typeLogin: chrome.i18n.getMessage("typeLogin"), updateLoginAction: chrome.i18n.getMessage("updateLoginAction"), - updateLoginPrompt: chrome.i18n.getMessage("updateLoginPrompt"), + updateLogin: chrome.i18n.getMessage("updateLogin"), + vault: chrome.i18n.getMessage("vault"), view: chrome.i18n.getMessage("view"), }; } @@ -127,7 +135,11 @@ async function initNotificationBar(message: NotificationBarWindowMessage) { } notificationBarIframeInitData = initData; - const { isVaultLocked, theme } = notificationBarIframeInitData; + const { + isVaultLocked, + removeIndividualVault: personalVaultDisallowed, // renamed to avoid local method collision + theme, + } = notificationBarIframeInitData; const i18n = getI18n(); const resolvedTheme = getResolvedTheme(theme ?? ThemeTypes.Light); @@ -135,7 +147,7 @@ async function initNotificationBar(message: NotificationBarWindowMessage) { document.body.innerHTML = ""; // Current implementations utilize a require for scss files which creates the need to remove the node. document.head.querySelectorAll('link[rel="stylesheet"]').forEach((node) => node.remove()); - + const orgId = selectedVaultSignal.get(); await Promise.all([ new Promise((resolve) => sendPlatformMessage({ command: "bgGetOrgData" }, resolve), @@ -146,19 +158,25 @@ async function initNotificationBar(message: NotificationBarWindowMessage) { new Promise((resolve) => sendPlatformMessage({ command: "bgGetDecryptedCiphers" }, resolve), ), - ]).then(([organizations, folders, ciphers]) => { + new Promise((resolve) => + sendPlatformMessage({ command: "bgGetCollectionData", orgId }, resolve), + ), + ]).then(([organizations, folders, ciphers, collections]) => { notificationBarIframeInitData = { ...notificationBarIframeInitData, + organizations, folders, ciphers, - organizations, + collections, }; + // @TODO use context to avoid prop drilling return render( NotificationContainer({ ...notificationBarIframeInitData, type: notificationBarIframeInitData.type as NotificationType, theme: resolvedTheme, + personalVaultIsAllowed: !personalVaultDisallowed, handleCloseNotification, handleSaveAction, handleEditOrUpdateAction, @@ -200,7 +218,7 @@ async function initNotificationBar(message: NotificationBarWindowMessage) { const changeTemplate = document.getElementById("template-change") as HTMLTemplateElement; const changeButton = findElementById(changeTemplate, "change-save"); - changeButton.textContent = i18n.notificationChangeSave; + changeButton.textContent = i18n.notificationUpdate; const changeEditButton = findElementById(changeTemplate, "change-edit"); changeEditButton.textContent = i18n.notificationEdit; @@ -249,9 +267,21 @@ function handleCloseNotification(e: Event) { } function handleSaveAction(e: Event) { + const selectedVault = selectedVaultSignal.get(); + const selectedFolder = selectedFolderSignal.get(); + + if (selectedVault.length > 1) { + openAddEditVaultItemPopout(e, { + organizationId: selectedVault, + folder: selectedFolder, + }); + handleCloseNotification(e); + return; + } + e.preventDefault(); - sendSaveCipherMessage(removeIndividualVault()); + sendSaveCipherMessage(removeIndividualVault(), selectedFolder); if (removeIndividualVault()) { return; } @@ -346,10 +376,24 @@ function handleSaveCipherAttemptCompletedMessage(message: NotificationBarWindowM ); } -function openViewVaultItemPopout(e: Event, cipherId: string) { +function openAddEditVaultItemPopout( + e: Event, + options: { + cipherId?: string; + organizationId?: string; + folder?: string; + }, +) { e.preventDefault(); sendPlatformMessage({ - command: "bgOpenVault", + command: "bgOpenAddEditVaultItemPopout", + ...options, + }); +} + +function openViewVaultItemPopout(cipherId: string) { + sendPlatformMessage({ + command: "bgOpenViewVaultItemPopout", cipherId, }); } @@ -357,7 +401,7 @@ function openViewVaultItemPopout(e: Event, cipherId: string) { function handleSaveCipherConfirmation(message: NotificationBarWindowMessage) { const { theme, type } = notificationBarIframeInitData; const { error, data } = message; - const { username, cipherId, task } = data || {}; + const { cipherId, task, itemName } = data || {}; const i18n = getI18n(); const resolvedTheme = getResolvedTheme(theme ?? ThemeTypes.Light); @@ -371,9 +415,9 @@ function handleSaveCipherConfirmation(message: NotificationBarWindowMessage) { handleCloseNotification, i18n, error, - username: username ?? i18n.typeLogin, + itemName: itemName ?? i18n.typeLogin, task, - handleOpenVault: (e) => cipherId && openViewVaultItemPopout(e, cipherId), + handleOpenVault: () => cipherId && openViewVaultItemPopout(cipherId), handleOpenTasks: () => sendPlatformMessage({ command: "bgOpenAtRisksPasswords" }), }), document.body, diff --git a/apps/browser/src/autofill/services/autofill-overlay-content.service.ts b/apps/browser/src/autofill/services/autofill-overlay-content.service.ts index 55260cc1149..dc8f45d104b 100644 --- a/apps/browser/src/autofill/services/autofill-overlay-content.service.ts +++ b/apps/browser/src/autofill/services/autofill-overlay-content.service.ts @@ -1128,6 +1128,11 @@ export class AutofillOverlayContentService implements AutofillOverlayContentServ * @param autofillFieldData - Autofill field data captured from the form field element. */ private qualifyAccountCreationFieldType(autofillFieldData: AutofillField) { + if (this.inlineMenuFieldQualificationService.isTotpField(autofillFieldData)) { + autofillFieldData.accountCreationFieldType = InlineMenuAccountCreationFieldType.Totp; + return; + } + if (!this.inlineMenuFieldQualificationService.isUsernameField(autofillFieldData)) { autofillFieldData.accountCreationFieldType = InlineMenuAccountCreationFieldType.Password; return; diff --git a/apps/browser/src/autofill/services/autofill.service.ts b/apps/browser/src/autofill/services/autofill.service.ts index 0423078fd1c..da46ceb0864 100644 --- a/apps/browser/src/autofill/services/autofill.service.ts +++ b/apps/browser/src/autofill/services/autofill.service.ts @@ -931,28 +931,37 @@ export default class AutofillService implements AutofillServiceInterface { } if (!passwordFields.length) { - // No password fields on this page. Let's try to just fuzzy fill the username. - pageDetails.fields.forEach((f) => { - if ( - !options.skipUsernameOnlyFill && - f.viewable && - (f.type === "text" || f.type === "email" || f.type === "tel") && - AutofillService.fieldIsFuzzyMatch(f, AutoFillConstants.UsernameFieldNames) - ) { - usernames.push(f); + // If there are no passwords, username or TOTP fields may be present. + // username and TOTP fields are mutually exclusive + pageDetails.fields.forEach((field) => { + if (!field.viewable) { + return; } - if ( + const isFillableTotpField = options.allowTotpAutofill && - f.viewable && - (f.type === "text" || f.type === "number") && - (AutofillService.fieldIsFuzzyMatch(f, [ + ["number", "tel", "text"].some((t) => t === field.type) && + (AutofillService.fieldIsFuzzyMatch(field, [ ...AutoFillConstants.TotpFieldNames, ...AutoFillConstants.AmbiguousTotpFieldNames, ]) || - f.autoCompleteType === "one-time-code") - ) { - totps.push(f); + field.autoCompleteType === "one-time-code"); + + const isFillableUsernameField = + !options.skipUsernameOnlyFill && + ["email", "tel", "text"].some((t) => t === field.type) && + AutofillService.fieldIsFuzzyMatch(field, AutoFillConstants.UsernameFieldNames); + + // Prefer more uniquely keyworded fields first. + switch (true) { + case isFillableTotpField: + totps.push(field); + return; + case isFillableUsernameField: + usernames.push(field); + return; + default: + return; } }); } @@ -2903,52 +2912,46 @@ export default class AutofillService implements AutofillServiceInterface { /** * Accepts a field and returns true if the field contains a * value that matches any of the names in the provided list. + * + * Returns boolean and attr of value that was matched as a tuple if showMatch is set to true. + * * @param {AutofillField} field * @param {string[]} names - * @returns {boolean} + * @param {boolean} showMatch + * @returns {boolean | [boolean, { attr: string; value: string }?]} */ - static fieldIsFuzzyMatch(field: AutofillField, names: string[]): boolean { - if (AutofillService.hasValue(field.htmlID) && this.fuzzyMatch(names, field.htmlID)) { - return true; - } - if (AutofillService.hasValue(field.htmlName) && this.fuzzyMatch(names, field.htmlName)) { - return true; - } - if ( - AutofillService.hasValue(field["label-tag"]) && - this.fuzzyMatch(names, field["label-tag"]) - ) { - return true; - } - if (AutofillService.hasValue(field.placeholder) && this.fuzzyMatch(names, field.placeholder)) { - return true; - } - if ( - AutofillService.hasValue(field["label-left"]) && - this.fuzzyMatch(names, field["label-left"]) - ) { - return true; - } - if ( - AutofillService.hasValue(field["label-top"]) && - this.fuzzyMatch(names, field["label-top"]) - ) { - return true; - } - if ( - AutofillService.hasValue(field["label-aria"]) && - this.fuzzyMatch(names, field["label-aria"]) - ) { - return true; - } - if ( - AutofillService.hasValue(field.dataSetValues) && - this.fuzzyMatch(names, field.dataSetValues) - ) { - return true; - } + static fieldIsFuzzyMatch( + field: AutofillField, + names: string[], + showMatch: true, + ): [boolean, { attr: string; value: string }?]; + static fieldIsFuzzyMatch(field: AutofillField, names: string[]): boolean; + static fieldIsFuzzyMatch( + field: AutofillField, + names: string[], + showMatch: boolean = false, + ): boolean | [boolean, { attr: string; value: string }?] { + const attrs = [ + "htmlID", + "htmlName", + "label-tag", + "placeholder", + "label-left", + "label-top", + "label-aria", + "dataSetValues", + ]; - return false; + for (const attr of attrs) { + const value = field[attr]; + if (!AutofillService.hasValue(value)) { + continue; + } + if (this.fuzzyMatch(names, value)) { + return showMatch ? [true, { attr, value }] : true; + } + } + return showMatch ? [false] : false; } /** diff --git a/apps/browser/src/autofill/shared/styles/webfonts.scss b/apps/browser/src/autofill/shared/styles/webfonts.scss index 6433060c534..20d0eda0622 100644 --- a/apps/browser/src/autofill/shared/styles/webfonts.scss +++ b/apps/browser/src/autofill/shared/styles/webfonts.scss @@ -62,14 +62,15 @@ font-display: swap; src: url(data:font/woff2;base64,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) format("woff2"); - unicode-range: U+0302-0303, U+0305, U+0307-0308, U+0330, U+0391-03A1, U+03A3-03A9, U+03B1-03C9, - U+03D1, U+03D5-03D6, U+03F0-03F1, U+03F4-03F5, U+2034-2037, U+2057, U+20D0-20DC, U+20E1, - U+20E5-20EF, U+2102, U+210A-210E, U+2110-2112, U+2115, U+2119-211D, U+2124, U+2128, U+212C-212D, - U+212F-2131, U+2133-2138, U+213C-2140, U+2145-2149, U+2190, U+2192, U+2194-21AE, U+21B0-21E5, - U+21F1-21F2, U+21F4-2211, U+2213-2214, U+2216-22FF, U+2308-230B, U+2310, U+2319, U+231C-2321, - U+2336-237A, U+237C, U+2395, U+239B-23B6, U+23D0, U+23DC-23E1, U+2474-2475, U+25AF, U+25B3, - U+25B7, U+25BD, U+25C1, U+25CA, U+25CC, U+25FB, U+266D-266F, U+27C0-27FF, U+2900-2AFF, - U+2B0E-2B11, U+2B30-2B4C, U+2BFE, U+FF5B, U+FF5D, U+1D400-1D7FF, U+1EE00-1EEFF; + unicode-range: + U+0302-0303, U+0305, U+0307-0308, U+0330, U+0391-03A1, U+03A3-03A9, U+03B1-03C9, U+03D1, + U+03D5-03D6, U+03F0-03F1, U+03F4-03F5, U+2034-2037, U+2057, U+20D0-20DC, U+20E1, U+20E5-20EF, + U+2102, U+210A-210E, U+2110-2112, U+2115, U+2119-211D, U+2124, U+2128, U+212C-212D, U+212F-2131, + U+2133-2138, U+213C-2140, U+2145-2149, U+2190, U+2192, U+2194-21AE, U+21B0-21E5, U+21F1-21F2, + U+21F4-2211, U+2213-2214, U+2216-22FF, U+2308-230B, U+2310, U+2319, U+231C-2321, U+2336-237A, + U+237C, U+2395, U+239B-23B6, U+23D0, U+23DC-23E1, U+2474-2475, U+25AF, U+25B3, U+25B7, U+25BD, + U+25C1, U+25CA, U+25CC, U+25FB, U+266D-266F, U+27C0-27FF, U+2900-2AFF, U+2B0E-2B11, U+2B30-2B4C, + U+2BFE, U+FF5B, U+FF5D, U+1D400-1D7FF, U+1EE00-1EEFF; } /* symbols */ @font-face { @@ -80,13 +81,14 @@ font-display: swap; src: url(data:font/woff2;base64,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) format("woff2"); - unicode-range: U+0001-000C, U+000E-001F, U+007F-009F, U+20DD-20E0, U+20E2-20E4, U+2150-218F, - U+2190, U+2192, U+2194-2199, U+21AF, U+21E6-21F0, U+21F3, U+2218-2219, U+2299, U+22C4-22C6, - U+2300-243F, U+2440-244A, U+2460-24FF, U+25A0-27BF, U+2800-28FF, U+2921-2922, U+2981, U+29BF, - U+29EB, U+2B00-2BFF, U+4DC0-4DFF, U+FFF9-FFFB, U+10140-1018E, U+10190-1019C, U+101A0, - U+101D0-101FD, U+102E0-102FB, U+10E60-10E7E, U+1D2C0-1D2D3, U+1D2E0-1D37F, U+1F000-1F0FF, - U+1F100-1F1AD, U+1F1E6-1F1FF, U+1F30D-1F30F, U+1F315, U+1F31C, U+1F31E, U+1F320-1F32C, U+1F336, - U+1F378, U+1F37D, U+1F382, U+1F393-1F39F, U+1F3A7-1F3A8, U+1F3AC-1F3AF, U+1F3C2, U+1F3C4-1F3C6, + unicode-range: + U+0001-000C, U+000E-001F, U+007F-009F, U+20DD-20E0, U+20E2-20E4, U+2150-218F, U+2190, U+2192, + U+2194-2199, U+21AF, U+21E6-21F0, U+21F3, U+2218-2219, U+2299, U+22C4-22C6, U+2300-243F, + U+2440-244A, U+2460-24FF, U+25A0-27BF, U+2800-28FF, U+2921-2922, U+2981, U+29BF, U+29EB, + U+2B00-2BFF, U+4DC0-4DFF, U+FFF9-FFFB, U+10140-1018E, U+10190-1019C, U+101A0, U+101D0-101FD, + U+102E0-102FB, U+10E60-10E7E, U+1D2C0-1D2D3, U+1D2E0-1D37F, U+1F000-1F0FF, U+1F100-1F1AD, + U+1F1E6-1F1FF, U+1F30D-1F30F, U+1F315, U+1F31C, U+1F31E, U+1F320-1F32C, U+1F336, U+1F378, + U+1F37D, U+1F382, U+1F393-1F39F, U+1F3A7-1F3A8, U+1F3AC-1F3AF, U+1F3C2, U+1F3C4-1F3C6, U+1F3CA-1F3CE, U+1F3D4-1F3E0, U+1F3ED, U+1F3F1-1F3F3, U+1F3F5-1F3F7, U+1F408, U+1F415, U+1F41F, U+1F426, U+1F43F, U+1F441-1F442, U+1F444, U+1F446-1F449, U+1F44C-1F44E, U+1F453, U+1F46A, U+1F47D, U+1F4A3, U+1F4B0, U+1F4B3, U+1F4B9, U+1F4BB, U+1F4BF, U+1F4C8-1F4CB, U+1F4D6, U+1F4DA, @@ -108,8 +110,9 @@ font-display: swap; src: url(data:font/woff2;base64,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) format("woff2"); - unicode-range: U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1, U+01AF-01B0, - U+0300-0301, U+0303-0304, U+0308-0309, U+0323, U+0329, U+1EA0-1EF9, U+20AB; + unicode-range: + U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1, U+01AF-01B0, U+0300-0301, + U+0303-0304, U+0308-0309, U+0323, U+0329, U+1EA0-1EF9, U+20AB; } /* latin-ext */ @font-face { @@ -120,8 +123,9 @@ font-display: swap; src: url(data:font/woff2;base64,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) format("woff2"); - unicode-range: U+0100-02AF, U+0304, U+0308, U+0329, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, - U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF; + unicode-range: + U+0100-02AF, U+0304, U+0308, U+0329, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, + U+2113, U+2C60-2C7F, U+A720-A7FF; } /* latin */ @font-face { @@ -132,9 +136,9 @@ font-display: swap; src: url(data:font/woff2;base64,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) format("woff2"); - unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, - U+0308, U+0329, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, - U+FFFD; + unicode-range: + U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, + U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD; } /* cyrillic-ext */ @@ -201,14 +205,15 @@ font-display: swap; src: url(data:font/woff2;base64,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) format("woff2"); - unicode-range: U+0302-0303, U+0305, U+0307-0308, U+0330, U+0391-03A1, U+03A3-03A9, U+03B1-03C9, - U+03D1, U+03D5-03D6, U+03F0-03F1, U+03F4-03F5, U+2034-2037, U+2057, U+20D0-20DC, U+20E1, - U+20E5-20EF, U+2102, U+210A-210E, U+2110-2112, U+2115, U+2119-211D, U+2124, U+2128, U+212C-212D, - U+212F-2131, U+2133-2138, U+213C-2140, U+2145-2149, U+2190, U+2192, U+2194-21AE, U+21B0-21E5, - U+21F1-21F2, U+21F4-2211, U+2213-2214, U+2216-22FF, U+2308-230B, U+2310, U+2319, U+231C-2321, - U+2336-237A, U+237C, U+2395, U+239B-23B6, U+23D0, U+23DC-23E1, U+2474-2475, U+25AF, U+25B3, - U+25B7, U+25BD, U+25C1, U+25CA, U+25CC, U+25FB, U+266D-266F, U+27C0-27FF, U+2900-2AFF, - U+2B0E-2B11, U+2B30-2B4C, U+2BFE, U+FF5B, U+FF5D, U+1D400-1D7FF, U+1EE00-1EEFF; + unicode-range: + U+0302-0303, U+0305, U+0307-0308, U+0330, U+0391-03A1, U+03A3-03A9, U+03B1-03C9, U+03D1, + U+03D5-03D6, U+03F0-03F1, U+03F4-03F5, U+2034-2037, U+2057, U+20D0-20DC, U+20E1, U+20E5-20EF, + U+2102, U+210A-210E, U+2110-2112, U+2115, U+2119-211D, U+2124, U+2128, U+212C-212D, U+212F-2131, + U+2133-2138, U+213C-2140, U+2145-2149, U+2190, U+2192, U+2194-21AE, U+21B0-21E5, U+21F1-21F2, + U+21F4-2211, U+2213-2214, U+2216-22FF, U+2308-230B, U+2310, U+2319, U+231C-2321, U+2336-237A, + U+237C, U+2395, U+239B-23B6, U+23D0, U+23DC-23E1, U+2474-2475, U+25AF, U+25B3, U+25B7, U+25BD, + U+25C1, U+25CA, U+25CC, U+25FB, U+266D-266F, U+27C0-27FF, U+2900-2AFF, U+2B0E-2B11, U+2B30-2B4C, + U+2BFE, U+FF5B, U+FF5D, U+1D400-1D7FF, U+1EE00-1EEFF; } /* symbols */ @font-face { @@ -219,13 +224,14 @@ font-display: swap; src: url(data:font/woff2;base64,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) format("woff2"); - unicode-range: U+0001-000C, U+000E-001F, U+007F-009F, U+20DD-20E0, U+20E2-20E4, U+2150-218F, - U+2190, U+2192, U+2194-2199, U+21AF, U+21E6-21F0, U+21F3, U+2218-2219, U+2299, U+22C4-22C6, - U+2300-243F, U+2440-244A, U+2460-24FF, U+25A0-27BF, U+2800-28FF, U+2921-2922, U+2981, U+29BF, - U+29EB, U+2B00-2BFF, U+4DC0-4DFF, U+FFF9-FFFB, U+10140-1018E, U+10190-1019C, U+101A0, - U+101D0-101FD, U+102E0-102FB, U+10E60-10E7E, U+1D2C0-1D2D3, U+1D2E0-1D37F, U+1F000-1F0FF, - U+1F100-1F1AD, U+1F1E6-1F1FF, U+1F30D-1F30F, U+1F315, U+1F31C, U+1F31E, U+1F320-1F32C, U+1F336, - U+1F378, U+1F37D, U+1F382, U+1F393-1F39F, U+1F3A7-1F3A8, U+1F3AC-1F3AF, U+1F3C2, U+1F3C4-1F3C6, + unicode-range: + U+0001-000C, U+000E-001F, U+007F-009F, U+20DD-20E0, U+20E2-20E4, U+2150-218F, U+2190, U+2192, + U+2194-2199, U+21AF, U+21E6-21F0, U+21F3, U+2218-2219, U+2299, U+22C4-22C6, U+2300-243F, + U+2440-244A, U+2460-24FF, U+25A0-27BF, U+2800-28FF, U+2921-2922, U+2981, U+29BF, U+29EB, + U+2B00-2BFF, U+4DC0-4DFF, U+FFF9-FFFB, U+10140-1018E, U+10190-1019C, U+101A0, U+101D0-101FD, + U+102E0-102FB, U+10E60-10E7E, U+1D2C0-1D2D3, U+1D2E0-1D37F, U+1F000-1F0FF, U+1F100-1F1AD, + U+1F1E6-1F1FF, U+1F30D-1F30F, U+1F315, U+1F31C, U+1F31E, U+1F320-1F32C, U+1F336, U+1F378, + U+1F37D, U+1F382, U+1F393-1F39F, U+1F3A7-1F3A8, U+1F3AC-1F3AF, U+1F3C2, U+1F3C4-1F3C6, U+1F3CA-1F3CE, U+1F3D4-1F3E0, U+1F3ED, U+1F3F1-1F3F3, U+1F3F5-1F3F7, U+1F408, U+1F415, U+1F41F, U+1F426, U+1F43F, U+1F441-1F442, U+1F444, U+1F446-1F449, U+1F44C-1F44E, U+1F453, U+1F46A, U+1F47D, U+1F4A3, U+1F4B0, U+1F4B3, U+1F4B9, U+1F4BB, U+1F4BF, U+1F4C8-1F4CB, U+1F4D6, U+1F4DA, @@ -247,8 +253,9 @@ font-display: swap; src: url(data:font/woff2;base64,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) format("woff2"); - unicode-range: U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1, U+01AF-01B0, - U+0300-0301, U+0303-0304, U+0308-0309, U+0323, U+0329, U+1EA0-1EF9, U+20AB; + unicode-range: + U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1, U+01AF-01B0, U+0300-0301, + U+0303-0304, U+0308-0309, U+0323, U+0329, U+1EA0-1EF9, U+20AB; } /* latin-ext */ @font-face { @@ -259,8 +266,9 @@ font-display: swap; src: url(data:font/woff2;base64,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) format("woff2"); - unicode-range: U+0100-02AF, U+0304, U+0308, U+0329, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, - U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF; + unicode-range: + U+0100-02AF, U+0304, U+0308, U+0329, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, + U+2113, U+2C60-2C7F, U+A720-A7FF; } /* latin */ @font-face { @@ -271,9 +279,9 @@ font-display: swap; src: url(data:font/woff2;base64,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) format("woff2"); - unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, - U+0308, U+0329, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, - U+FFFD; + unicode-range: + U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, + U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD; } /* cyrillic-ext */ @@ -340,14 +348,15 @@ font-display: swap; src: url(data:font/woff2;base64,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) format("woff2"); - unicode-range: U+0302-0303, U+0305, U+0307-0308, U+0330, U+0391-03A1, U+03A3-03A9, U+03B1-03C9, - U+03D1, U+03D5-03D6, U+03F0-03F1, U+03F4-03F5, U+2034-2037, U+2057, U+20D0-20DC, U+20E1, - U+20E5-20EF, U+2102, U+210A-210E, U+2110-2112, U+2115, U+2119-211D, U+2124, U+2128, U+212C-212D, - U+212F-2131, U+2133-2138, U+213C-2140, U+2145-2149, U+2190, U+2192, U+2194-21AE, U+21B0-21E5, - U+21F1-21F2, U+21F4-2211, U+2213-2214, U+2216-22FF, U+2308-230B, U+2310, U+2319, U+231C-2321, - U+2336-237A, U+237C, U+2395, U+239B-23B6, U+23D0, U+23DC-23E1, U+2474-2475, U+25AF, U+25B3, - U+25B7, U+25BD, U+25C1, U+25CA, U+25CC, U+25FB, U+266D-266F, U+27C0-27FF, U+2900-2AFF, - U+2B0E-2B11, U+2B30-2B4C, U+2BFE, U+FF5B, U+FF5D, U+1D400-1D7FF, U+1EE00-1EEFF; + unicode-range: + U+0302-0303, U+0305, U+0307-0308, U+0330, U+0391-03A1, U+03A3-03A9, U+03B1-03C9, U+03D1, + U+03D5-03D6, U+03F0-03F1, U+03F4-03F5, U+2034-2037, U+2057, U+20D0-20DC, U+20E1, U+20E5-20EF, + U+2102, U+210A-210E, U+2110-2112, U+2115, U+2119-211D, U+2124, U+2128, U+212C-212D, U+212F-2131, + U+2133-2138, U+213C-2140, U+2145-2149, U+2190, U+2192, U+2194-21AE, U+21B0-21E5, U+21F1-21F2, + U+21F4-2211, U+2213-2214, U+2216-22FF, U+2308-230B, U+2310, U+2319, U+231C-2321, U+2336-237A, + U+237C, U+2395, U+239B-23B6, U+23D0, U+23DC-23E1, U+2474-2475, U+25AF, U+25B3, U+25B7, U+25BD, + U+25C1, U+25CA, U+25CC, U+25FB, U+266D-266F, U+27C0-27FF, U+2900-2AFF, U+2B0E-2B11, U+2B30-2B4C, + U+2BFE, U+FF5B, U+FF5D, U+1D400-1D7FF, U+1EE00-1EEFF; } /* symbols */ @font-face { @@ -358,13 +367,14 @@ font-display: swap; src: url(data:font/woff2;base64,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) format("woff2"); - unicode-range: U+0001-000C, U+000E-001F, U+007F-009F, U+20DD-20E0, U+20E2-20E4, U+2150-218F, - U+2190, U+2192, U+2194-2199, U+21AF, U+21E6-21F0, U+21F3, U+2218-2219, U+2299, U+22C4-22C6, - U+2300-243F, U+2440-244A, U+2460-24FF, U+25A0-27BF, U+2800-28FF, U+2921-2922, U+2981, U+29BF, - U+29EB, U+2B00-2BFF, U+4DC0-4DFF, U+FFF9-FFFB, U+10140-1018E, U+10190-1019C, U+101A0, - U+101D0-101FD, U+102E0-102FB, U+10E60-10E7E, U+1D2C0-1D2D3, U+1D2E0-1D37F, U+1F000-1F0FF, - U+1F100-1F1AD, U+1F1E6-1F1FF, U+1F30D-1F30F, U+1F315, U+1F31C, U+1F31E, U+1F320-1F32C, U+1F336, - U+1F378, U+1F37D, U+1F382, U+1F393-1F39F, U+1F3A7-1F3A8, U+1F3AC-1F3AF, U+1F3C2, U+1F3C4-1F3C6, + unicode-range: + U+0001-000C, U+000E-001F, U+007F-009F, U+20DD-20E0, U+20E2-20E4, U+2150-218F, U+2190, U+2192, + U+2194-2199, U+21AF, U+21E6-21F0, U+21F3, U+2218-2219, U+2299, U+22C4-22C6, U+2300-243F, + U+2440-244A, U+2460-24FF, U+25A0-27BF, U+2800-28FF, U+2921-2922, U+2981, U+29BF, U+29EB, + U+2B00-2BFF, U+4DC0-4DFF, U+FFF9-FFFB, U+10140-1018E, U+10190-1019C, U+101A0, U+101D0-101FD, + U+102E0-102FB, U+10E60-10E7E, U+1D2C0-1D2D3, U+1D2E0-1D37F, U+1F000-1F0FF, U+1F100-1F1AD, + U+1F1E6-1F1FF, U+1F30D-1F30F, U+1F315, U+1F31C, U+1F31E, U+1F320-1F32C, U+1F336, U+1F378, + U+1F37D, U+1F382, U+1F393-1F39F, U+1F3A7-1F3A8, U+1F3AC-1F3AF, U+1F3C2, U+1F3C4-1F3C6, U+1F3CA-1F3CE, U+1F3D4-1F3E0, U+1F3ED, U+1F3F1-1F3F3, U+1F3F5-1F3F7, U+1F408, U+1F415, U+1F41F, U+1F426, U+1F43F, U+1F441-1F442, U+1F444, U+1F446-1F449, U+1F44C-1F44E, U+1F453, U+1F46A, U+1F47D, U+1F4A3, U+1F4B0, U+1F4B3, U+1F4B9, U+1F4BB, U+1F4BF, U+1F4C8-1F4CB, U+1F4D6, U+1F4DA, @@ -386,8 +396,9 @@ font-display: swap; src: url(data:font/woff2;base64,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) format("woff2"); - unicode-range: U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1, U+01AF-01B0, - U+0300-0301, U+0303-0304, U+0308-0309, U+0323, U+0329, U+1EA0-1EF9, U+20AB; + unicode-range: + U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1, U+01AF-01B0, U+0300-0301, + U+0303-0304, U+0308-0309, U+0323, U+0329, U+1EA0-1EF9, U+20AB; } /* latin-ext */ @font-face { @@ -398,8 +409,9 @@ font-display: swap; src: url(data:font/woff2;base64,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) format("woff2"); - unicode-range: U+0100-02AF, U+0304, U+0308, U+0329, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, - U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF; + unicode-range: + U+0100-02AF, U+0304, U+0308, U+0329, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, + U+2113, U+2C60-2C7F, U+A720-A7FF; } /* latin */ @font-face { @@ -410,9 +422,9 @@ font-display: swap; src: url(data:font/woff2;base64,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) format("woff2"); - unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, - U+0308, U+0329, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, - U+FFFD; + unicode-range: + U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, + U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD; } /* cyrillic-ext */ @@ -463,8 +475,9 @@ font-display: swap; src: url(data:font/woff2;base64,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) format("woff2"); - unicode-range: U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1, U+01AF-01B0, - U+0300-0301, U+0303-0304, U+0308-0309, U+0323, U+0329, U+1EA0-1EF9, U+20AB; + unicode-range: + U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1, U+01AF-01B0, U+0300-0301, + U+0303-0304, U+0308-0309, U+0323, U+0329, U+1EA0-1EF9, U+20AB; } /* latin-ext */ @font-face { @@ -474,8 +487,9 @@ font-display: swap; src: url(data:font/woff2;base64,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) format("woff2"); - unicode-range: U+0100-02AF, U+0304, U+0308, U+0329, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, - U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF; + unicode-range: + U+0100-02AF, U+0304, U+0308, U+0329, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, + U+2113, U+2C60-2C7F, U+A720-A7FF; } /* latin */ @font-face { @@ -485,7 +499,7 @@ font-display: swap; src: url(data:font/woff2;base64,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) format("woff2"); - unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, - U+0308, U+0329, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, - U+FFFD; + unicode-range: + U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, + U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD; } diff --git a/apps/browser/src/background/main.background.ts b/apps/browser/src/background/main.background.ts index 2b6827aafa4..da47542ee6b 100644 --- a/apps/browser/src/background/main.background.ts +++ b/apps/browser/src/background/main.background.ts @@ -191,6 +191,10 @@ import { InternalFolderService as InternalFolderServiceAbstraction } from "@bitw import { TotpService as TotpServiceAbstraction } from "@bitwarden/common/vault/abstractions/totp.service"; import { VaultSettingsService as VaultSettingsServiceAbstraction } from "@bitwarden/common/vault/abstractions/vault-settings/vault-settings.service"; import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view"; +import { + DefaultEndUserNotificationService, + EndUserNotificationService, +} from "@bitwarden/common/vault/notifications"; import { CipherAuthorizationService, DefaultCipherAuthorizationService, @@ -402,6 +406,7 @@ export default class MainBackground { sdkService: SdkService; sdkLoadService: SdkLoadService; cipherAuthorizationService: CipherAuthorizationService; + endUserNotificationService: EndUserNotificationService; inlineMenuFieldQualificationService: InlineMenuFieldQualificationService; taskService: TaskService; @@ -1201,6 +1206,7 @@ export default class MainBackground { this.authService, this.autofillService, this.cipherService, + this.collectionService, this.configService, this.domainSettingsService, this.environmentService, @@ -1320,6 +1326,14 @@ export default class MainBackground { this.ipcContentScriptManagerService = new IpcContentScriptManagerService(this.configService); this.ipcService = new IpcBackgroundService(this.logService); + + this.endUserNotificationService = new DefaultEndUserNotificationService( + this.stateProvider, + this.apiService, + this.notificationsService, + this.authService, + this.logService, + ); } async bootstrap() { @@ -1406,6 +1420,9 @@ export default class MainBackground { this.taskService.listenForTaskNotifications(); } + if (await this.configService.getFeatureFlag(FeatureFlag.EndUserNotifications)) { + this.endUserNotificationService.listenForEndUserNotifications(); + } resolve(); }, 500); }); diff --git a/apps/browser/src/key-management/lock/services/extension-lock-component.service.spec.ts b/apps/browser/src/key-management/lock/services/extension-lock-component.service.spec.ts index 9afc723825c..ac5331d3627 100644 --- a/apps/browser/src/key-management/lock/services/extension-lock-component.service.spec.ts +++ b/apps/browser/src/key-management/lock/services/extension-lock-component.service.spec.ts @@ -17,6 +17,8 @@ import { } from "@bitwarden/key-management"; import { UnlockOptions } from "@bitwarden/key-management-ui"; +import { BrowserApi } from "../../../platform/browser/browser-api"; +import BrowserPopupUtils from "../../../platform/popup/browser-popup-utils"; import { BrowserRouterService } from "../../../platform/popup/services/browser-router.service"; import { ExtensionLockComponentService } from "./extension-lock-component.service"; @@ -117,6 +119,62 @@ describe("ExtensionLockComponentService", () => { }); }); + describe("popOutBrowserExtension", () => { + let openPopoutSpy: jest.SpyInstance; + beforeEach(() => { + jest.resetAllMocks(); + openPopoutSpy = jest + .spyOn(BrowserPopupUtils, "openCurrentPagePopout") + .mockResolvedValue(undefined); + }); + + it("opens pop-out when the current window is neither a pop-out nor a sidebar", async () => { + jest.spyOn(BrowserPopupUtils, "inPopout").mockReturnValue(false); + jest.spyOn(BrowserPopupUtils, "inSidebar").mockReturnValue(false); + + await service.popOutBrowserExtension(); + + expect(openPopoutSpy).toHaveBeenCalledWith(global.window); + }); + + test.each([ + [true, false], + [false, true], + [true, true], + ])("should not open pop-out under other conditions.", async (inPopout, inSidebar) => { + jest.spyOn(BrowserPopupUtils, "inPopout").mockReturnValue(inPopout); + jest.spyOn(BrowserPopupUtils, "inSidebar").mockReturnValue(inSidebar); + + await service.popOutBrowserExtension(); + + expect(openPopoutSpy).not.toHaveBeenCalled(); + }); + }); + + describe("closeBrowserExtensionPopout", () => { + let closePopupSpy: jest.SpyInstance; + beforeEach(() => { + jest.resetAllMocks(); + closePopupSpy = jest.spyOn(BrowserApi, "closePopup").mockReturnValue(); + }); + + it("closes pop-out when in pop-out", () => { + jest.spyOn(BrowserPopupUtils, "inPopout").mockReturnValue(true); + + service.closeBrowserExtensionPopout(); + + expect(closePopupSpy).toHaveBeenCalledWith(global.window); + }); + + it("doesn't close pop-out when not in pop-out", () => { + jest.spyOn(BrowserPopupUtils, "inPopout").mockReturnValue(false); + + service.closeBrowserExtensionPopout(); + + expect(closePopupSpy).not.toHaveBeenCalled(); + }); + }); + describe("isWindowVisible", () => { it("throws an error", async () => { await expect(service.isWindowVisible()).rejects.toThrow("Method not implemented."); diff --git a/apps/browser/src/key-management/lock/services/extension-lock-component.service.ts b/apps/browser/src/key-management/lock/services/extension-lock-component.service.ts index 09a6f890e60..6ee1fc5175f 100644 --- a/apps/browser/src/key-management/lock/services/extension-lock-component.service.ts +++ b/apps/browser/src/key-management/lock/services/extension-lock-component.service.ts @@ -14,6 +14,8 @@ import { import { LockComponentService, UnlockOptions } from "@bitwarden/key-management-ui"; import { BiometricErrors, BiometricErrorTypes } from "../../../models/biometricErrors"; +import { BrowserApi } from "../../../platform/browser/browser-api"; +import BrowserPopupUtils from "../../../platform/popup/browser-popup-utils"; import { BrowserRouterService } from "../../../platform/popup/services/browser-router.service"; export class ExtensionLockComponentService implements LockComponentService { @@ -37,6 +39,18 @@ export class ExtensionLockComponentService implements LockComponentService { return biometricsError.description; } + async popOutBrowserExtension(): Promise { + if (!BrowserPopupUtils.inPopout(global.window) && !BrowserPopupUtils.inSidebar(global.window)) { + await BrowserPopupUtils.openCurrentPagePopout(global.window); + } + } + + closeBrowserExtensionPopout(): void { + if (BrowserPopupUtils.inPopout(global.window)) { + BrowserApi.closePopup(global.window); + } + } + async isWindowVisible(): Promise { throw new Error("Method not implemented."); } diff --git a/apps/browser/src/manifest.json b/apps/browser/src/manifest.json index 35578fb8321..fca62568048 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": "2025.3.2", + "version": "2025.4.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 556a73ac15b..47e0cc465ef 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": "2025.3.2", + "version": "2025.4.0", "description": "__MSG_extDesc__", "default_locale": "en", "author": "Bitwarden Inc.", diff --git a/apps/browser/src/platform/popup/layout/popup-layout.stories.ts b/apps/browser/src/platform/popup/layout/popup-layout.stories.ts index 79412e6bce8..32c4f151a8f 100644 --- a/apps/browser/src/platform/popup/layout/popup-layout.stories.ts +++ b/apps/browser/src/platform/popup/layout/popup-layout.stories.ts @@ -80,7 +80,7 @@ class VaultComponent { @Component({ selector: "mock-add-button", template: ` - + Add @@ -340,6 +340,7 @@ export default { generator: "Generator", send: "Send", settings: "Settings", + labelWithNotification: (label: string) => `${label}: New Notification`, }); }, }, @@ -398,17 +399,64 @@ export default { type Story = StoryObj; -export const PopupTabNavigation: Story = { +type PopupTabNavigationStory = StoryObj; + +const navButtons = (showBerry = false) => [ + { + label: "vault", + page: "/tabs/vault", + iconKey: "lock", + iconKeyActive: "lock-f", + }, + { + label: "generator", + page: "/tabs/generator", + iconKey: "generate", + iconKeyActive: "generate-f", + }, + { + label: "send", + page: "/tabs/send", + iconKey: "send", + iconKeyActive: "send-f", + }, + { + label: "settings", + page: "/tabs/settings", + iconKey: "cog", + iconKeyActive: "cog-f", + showBerry: showBerry, + }, +]; + +export const DefaultPopupTabNavigation: PopupTabNavigationStory = { render: (args) => ({ props: args, - template: /* HTML */ ` + template: /*html*/ ` - + - - `, + `, }), + args: { + navButtons: navButtons(), + }, +}; + +export const PopupTabNavigationWithBerry: PopupTabNavigationStory = { + render: (args) => ({ + props: args, + template: /*html*/ ` + + + + + `, + }), + args: { + navButtons: navButtons(true), + }, }; export const PopupPage: Story = { diff --git a/apps/browser/src/platform/popup/layout/popup-tab-navigation.component.html b/apps/browser/src/platform/popup/layout/popup-tab-navigation.component.html index bed4eac3f90..27b546738c3 100644 --- a/apps/browser/src/platform/popup/layout/popup-tab-navigation.component.html +++ b/apps/browser/src/platform/popup/layout/popup-tab-navigation.component.html @@ -5,12 +5,13 @@ - + + + + diff --git a/apps/browser/src/platform/popup/layout/popup-tab-navigation.component.ts b/apps/browser/src/platform/popup/layout/popup-tab-navigation.component.ts index e01b4efd71b..f4b82dc56fc 100644 --- a/apps/browser/src/platform/popup/layout/popup-tab-navigation.component.ts +++ b/apps/browser/src/platform/popup/layout/popup-tab-navigation.component.ts @@ -1,10 +1,19 @@ import { CommonModule } from "@angular/common"; -import { Component } from "@angular/core"; +import { Component, Input } from "@angular/core"; import { RouterModule } from "@angular/router"; import { JslibModule } from "@bitwarden/angular/jslib.module"; +import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { LinkModule } from "@bitwarden/components"; +export type NavButton = { + label: string; + page: string; + iconKey: string; + iconKeyActive: string; + showBerry?: boolean; +}; + @Component({ selector: "popup-tab-navigation", templateUrl: "popup-tab-navigation.component.html", @@ -15,30 +24,12 @@ import { LinkModule } from "@bitwarden/components"; }, }) export class PopupTabNavigationComponent { - navButtons = [ - { - label: "vault", - page: "/tabs/vault", - iconKey: "lock", - iconKeyActive: "lock-f", - }, - { - label: "generator", - page: "/tabs/generator", - iconKey: "generate", - iconKeyActive: "generate-f", - }, - { - label: "send", - page: "/tabs/send", - iconKey: "send", - iconKeyActive: "send-f", - }, - { - label: "settings", - page: "/tabs/settings", - iconKey: "cog", - iconKeyActive: "cog-f", - }, - ]; + @Input() navButtons: NavButton[] = []; + + constructor(private i18nService: I18nService) {} + + buttonTitle(navButton: NavButton) { + const labelText = this.i18nService.t(navButton.label); + return navButton.showBerry ? this.i18nService.t("labelWithNotification", labelText) : labelText; + } } diff --git a/apps/browser/src/platform/popup/view-cache/popup-view-cache.service.ts b/apps/browser/src/platform/popup/view-cache/popup-view-cache.service.ts index 457198eaa4e..6fc3e11493c 100644 --- a/apps/browser/src/platform/popup/view-cache/popup-view-cache.service.ts +++ b/apps/browser/src/platform/popup/view-cache/popup-view-cache.service.ts @@ -27,6 +27,7 @@ import { ClEAR_VIEW_CACHE_COMMAND, POPUP_VIEW_CACHE_KEY, SAVE_VIEW_CACHE_COMMAND, + ViewCacheState, } from "../../services/popup-view-cache-background.service"; /** @@ -42,8 +43,8 @@ export class PopupViewCacheService implements ViewCacheService { private messageSender = inject(MessageSender); private router = inject(Router); - private _cache: Record; - private get cache(): Record { + private _cache: Record; + private get cache(): Record { if (!this._cache) { throw new Error("Dirty View Cache not initialized"); } @@ -64,15 +65,9 @@ export class PopupViewCacheService implements ViewCacheService { filter((e) => e instanceof NavigationEnd), /** Skip the first navigation triggered by `popupRouterCacheGuard` */ skip(1), - filter((e: NavigationEnd) => - // viewing/editing a cipher and navigating back to the vault list should not clear the cache - ["/view-cipher", "/edit-cipher", "/tabs/vault"].every( - (route) => !e.urlAfterRedirects.startsWith(route), - ), - ), ) - .subscribe((e) => { - return this.clearState(); + .subscribe(() => { + return this.clearState(true); }); } @@ -85,13 +80,20 @@ export class PopupViewCacheService implements ViewCacheService { key, injector = inject(Injector), initialValue, + persistNavigation, } = options; - const cachedValue = this.cache[key] ? deserializer(JSON.parse(this.cache[key])) : initialValue; + const cachedValue = this.cache[key] + ? deserializer(JSON.parse(this.cache[key].value)) + : initialValue; const _signal = signal(cachedValue); + const viewCacheOptions = { + ...(persistNavigation && { persistNavigation }), + }; + effect( () => { - this.updateState(key, JSON.stringify(_signal())); + this.updateState(key, JSON.stringify(_signal()), viewCacheOptions); }, { injector }, ); @@ -123,15 +125,24 @@ export class PopupViewCacheService implements ViewCacheService { return control; } - private updateState(key: string, value: string) { + private updateState(key: string, value: string, options: ViewCacheState["options"]) { this.messageSender.send(SAVE_VIEW_CACHE_COMMAND, { key, value, + options, }); } - private clearState() { - this._cache = {}; // clear local cache - this.messageSender.send(ClEAR_VIEW_CACHE_COMMAND, {}); + private clearState(routeChange: boolean = false) { + if (routeChange) { + // Only keep entries with `persistNavigation` + this._cache = Object.fromEntries( + Object.entries(this._cache).filter(([, { options }]) => options?.persistNavigation), + ); + } else { + // Clear all entries + this._cache = {}; + } + this.messageSender.send(ClEAR_VIEW_CACHE_COMMAND, { routeChange: routeChange }); } } diff --git a/apps/browser/src/platform/popup/view-cache/popup-view-cache.spec.ts b/apps/browser/src/platform/popup/view-cache/popup-view-cache.spec.ts index b6009c4cc2e..2ec75791d1b 100644 --- a/apps/browser/src/platform/popup/view-cache/popup-view-cache.spec.ts +++ b/apps/browser/src/platform/popup/view-cache/popup-view-cache.spec.ts @@ -14,6 +14,7 @@ import { ClEAR_VIEW_CACHE_COMMAND, POPUP_VIEW_CACHE_KEY, SAVE_VIEW_CACHE_COMMAND, + ViewCacheState, } from "../../services/popup-view-cache-background.service"; import { PopupViewCacheService } from "./popup-view-cache.service"; @@ -35,6 +36,7 @@ export class TestComponent { signal = this.viewCacheService.signal({ key: "test-signal", initialValue: "initial signal", + persistNavigation: true, }); } @@ -42,11 +44,11 @@ describe("popup view cache", () => { const configServiceMock = mock(); let testBed: TestBed; let service: PopupViewCacheService; - let fakeGlobalState: FakeGlobalState>; + let fakeGlobalState: FakeGlobalState>; let messageSenderMock: MockProxy; let router: Router; - const initServiceWithState = async (state: Record) => { + const initServiceWithState = async (state: Record) => { await fakeGlobalState.update(() => state); await service.init(); }; @@ -106,7 +108,11 @@ describe("popup view cache", () => { }); it("should initialize signal from state", async () => { - await initServiceWithState({ "foo-123": JSON.stringify("bar") }); + await initServiceWithState({ + "foo-123": { + value: JSON.stringify("bar"), + }, + }); const injector = TestBed.inject(Injector); @@ -120,7 +126,11 @@ describe("popup view cache", () => { }); it("should initialize form from state", async () => { - await initServiceWithState({ "test-form-cache": JSON.stringify({ name: "baz" }) }); + await initServiceWithState({ + "test-form-cache": { + value: JSON.stringify({ name: "baz" }), + }, + }); const fixture = TestBed.createComponent(TestComponent); const component = fixture.componentRef.instance; @@ -138,7 +148,11 @@ describe("popup view cache", () => { }); it("should utilize deserializer", async () => { - await initServiceWithState({ "foo-123": JSON.stringify("bar") }); + await initServiceWithState({ + "foo-123": { + value: JSON.stringify("bar"), + }, + }); const injector = TestBed.inject(Injector); @@ -178,6 +192,9 @@ describe("popup view cache", () => { expect(messageSenderMock.send).toHaveBeenCalledWith(SAVE_VIEW_CACHE_COMMAND, { key: "test-signal", value: JSON.stringify("Foobar"), + options: { + persistNavigation: true, + }, }); }); @@ -192,18 +209,63 @@ describe("popup view cache", () => { expect(messageSenderMock.send).toHaveBeenCalledWith(SAVE_VIEW_CACHE_COMMAND, { key: "test-form-cache", value: JSON.stringify({ name: "Foobar" }), + options: {}, }); }); it("should clear on 2nd navigation", async () => { - await initServiceWithState({ temp: "state" }); + await initServiceWithState({ + temp: { + value: "state", + options: {}, + }, + }); await router.navigate(["a"]); expect(messageSenderMock.send).toHaveBeenCalledTimes(0); - expect(service["_cache"]).toEqual({ temp: "state" }); + expect(service["_cache"]).toEqual({ + temp: { + value: "state", + options: {}, + }, + }); await router.navigate(["b"]); - expect(messageSenderMock.send).toHaveBeenCalledWith(ClEAR_VIEW_CACHE_COMMAND, {}); + expect(messageSenderMock.send).toHaveBeenCalledWith(ClEAR_VIEW_CACHE_COMMAND, { + routeChange: true, + }); expect(service["_cache"]).toEqual({}); }); + + it("should respect persistNavigation setting on 2nd navigation", async () => { + await initServiceWithState({ + keepState: { + value: "state", + options: { + persistNavigation: true, + }, + }, + removeState: { + value: "state", + options: { + persistNavigation: false, + }, + }, + }); + + await router.navigate(["a"]); // first navigation covered in previous test + + await router.navigate(["b"]); + expect(messageSenderMock.send).toHaveBeenCalledWith(ClEAR_VIEW_CACHE_COMMAND, { + routeChange: true, + }); + expect(service["_cache"]).toEqual({ + keepState: { + value: "state", + options: { + persistNavigation: true, + }, + }, + }); + }); }); diff --git a/apps/browser/src/platform/services/popup-view-cache-background.service.ts b/apps/browser/src/platform/services/popup-view-cache-background.service.ts index 98a6065189b..79c04e90aad 100644 --- a/apps/browser/src/platform/services/popup-view-cache-background.service.ts +++ b/apps/browser/src/platform/services/popup-view-cache-background.service.ts @@ -16,8 +16,27 @@ import { fromChromeEvent } from "../browser/from-chrome-event"; const popupClosedPortName = "new_popup"; +export type ViewCacheOptions = { + /** + * Optional flag to persist the cached value between navigation events. + */ + persistNavigation?: boolean; +}; + +export type ViewCacheState = { + /** + * The cached value + */ + value: string; // JSON value + + /** + * Options for managing/clearing the cache + */ + options?: ViewCacheOptions; +}; + /** We cannot use `UserKeyDefinition` because we must be able to store state when there is no active user. */ -export const POPUP_VIEW_CACHE_KEY = KeyDefinition.record( +export const POPUP_VIEW_CACHE_KEY = KeyDefinition.record( POPUP_VIEW_MEMORY, "popup-view-cache", { @@ -36,9 +55,15 @@ export const POPUP_ROUTE_HISTORY_KEY = new KeyDefinition( export const SAVE_VIEW_CACHE_COMMAND = new CommandDefinition<{ key: string; value: string; + options: ViewCacheOptions; }>("save-view-cache"); -export const ClEAR_VIEW_CACHE_COMMAND = new CommandDefinition("clear-view-cache"); +export const ClEAR_VIEW_CACHE_COMMAND = new CommandDefinition<{ + /** + * Flag to indicate the clear request was triggered by a route change in popup. + */ + routeChange: boolean; +}>("clear-view-cache"); export class PopupViewCacheBackgroundService { private popupViewCacheState = this.globalStateProvider.get(POPUP_VIEW_CACHE_KEY); @@ -61,10 +86,13 @@ export class PopupViewCacheBackgroundService { this.messageListener .messages$(SAVE_VIEW_CACHE_COMMAND) .pipe( - concatMap(async ({ key, value }) => + concatMap(async ({ key, value, options }) => this.popupViewCacheState.update((state) => ({ ...state, - [key]: value, + [key]: { + value, + options, + }, })), ), ) @@ -72,7 +100,19 @@ export class PopupViewCacheBackgroundService { this.messageListener .messages$(ClEAR_VIEW_CACHE_COMMAND) - .pipe(concatMap(() => this.popupViewCacheState.update(() => null))) + .pipe( + concatMap(({ routeChange }) => + this.popupViewCacheState.update((state) => { + if (routeChange && state) { + // Only remove keys that are not marked with `persistNavigation` + return Object.fromEntries( + Object.entries(state).filter(([, { options }]) => options?.persistNavigation), + ); + } + return null; + }), + ), + ) .subscribe(); // on popup closed, with 2 minute delay that is cancelled by re-opening the popup diff --git a/apps/browser/src/platform/sync/foreground-sync.service.spec.ts b/apps/browser/src/platform/sync/foreground-sync.service.spec.ts index f5daff93815..34ee4fa0f77 100644 --- a/apps/browser/src/platform/sync/foreground-sync.service.spec.ts +++ b/apps/browser/src/platform/sync/foreground-sync.service.spec.ts @@ -8,6 +8,7 @@ import { LogService } from "@bitwarden/common/platform/abstractions/log.service" import { StateService } from "@bitwarden/common/platform/abstractions/state.service"; import { MessageListener, MessageSender } from "@bitwarden/common/platform/messaging"; import { Utils } from "@bitwarden/common/platform/misc/utils"; +import { SyncOptions } from "@bitwarden/common/platform/sync/sync.service"; import { FakeStateProvider, mockAccountServiceWith } from "@bitwarden/common/spec"; import { SendApiService } from "@bitwarden/common/tools/send/services/send-api.service.abstraction"; import { InternalSendService } from "@bitwarden/common/tools/send/services/send.service.abstraction"; @@ -80,7 +81,72 @@ describe("ForegroundSyncService", () => { const fullSyncPromise = sut.fullSync(true, false); expect(sut.syncInProgress).toBe(true); - const requestId = getAndAssertRequestId({ forceSync: true, allowThrowOnError: false }); + const requestId = getAndAssertRequestId({ + forceSync: true, + options: { allowThrowOnError: false, skipTokenRefresh: false }, + }); + + // Pretend the sync has finished + messages.next({ successfully: true, errorMessage: null, requestId: requestId }); + + const result = await fullSyncPromise; + + expect(sut.syncInProgress).toBe(false); + expect(result).toBe(true); + }); + + const testData: { + input: boolean | SyncOptions | undefined; + normalized: Required; + }[] = [ + { + input: undefined, + normalized: { allowThrowOnError: false, skipTokenRefresh: false }, + }, + { + input: true, + normalized: { allowThrowOnError: true, skipTokenRefresh: false }, + }, + { + input: false, + normalized: { allowThrowOnError: false, skipTokenRefresh: false }, + }, + { + input: { allowThrowOnError: false }, + normalized: { allowThrowOnError: false, skipTokenRefresh: false }, + }, + { + input: { allowThrowOnError: true }, + normalized: { allowThrowOnError: true, skipTokenRefresh: false }, + }, + { + input: { allowThrowOnError: false, skipTokenRefresh: false }, + normalized: { allowThrowOnError: false, skipTokenRefresh: false }, + }, + { + input: { allowThrowOnError: true, skipTokenRefresh: false }, + normalized: { allowThrowOnError: true, skipTokenRefresh: false }, + }, + { + input: { allowThrowOnError: true, skipTokenRefresh: true }, + normalized: { allowThrowOnError: true, skipTokenRefresh: true }, + }, + { + input: { allowThrowOnError: false, skipTokenRefresh: true }, + normalized: { allowThrowOnError: false, skipTokenRefresh: true }, + }, + ]; + + it.each(testData)("normalize input $input options correctly", async ({ input, normalized }) => { + const messages = new Subject(); + messageListener.messages$.mockReturnValue(messages); + const fullSyncPromise = sut.fullSync(true, input); + expect(sut.syncInProgress).toBe(true); + + const requestId = getAndAssertRequestId({ + forceSync: true, + options: normalized, + }); // Pretend the sync has finished messages.next({ successfully: true, errorMessage: null, requestId: requestId }); @@ -97,7 +163,10 @@ describe("ForegroundSyncService", () => { const fullSyncPromise = sut.fullSync(false, false); expect(sut.syncInProgress).toBe(true); - const requestId = getAndAssertRequestId({ forceSync: false, allowThrowOnError: false }); + const requestId = getAndAssertRequestId({ + forceSync: false, + options: { allowThrowOnError: false, skipTokenRefresh: false }, + }); // Pretend the sync has finished messages.next({ @@ -118,7 +187,10 @@ describe("ForegroundSyncService", () => { const fullSyncPromise = sut.fullSync(true, true); expect(sut.syncInProgress).toBe(true); - const requestId = getAndAssertRequestId({ forceSync: true, allowThrowOnError: true }); + const requestId = getAndAssertRequestId({ + forceSync: true, + options: { allowThrowOnError: true, skipTokenRefresh: false }, + }); // Pretend the sync has finished messages.next({ diff --git a/apps/browser/src/platform/sync/foreground-sync.service.ts b/apps/browser/src/platform/sync/foreground-sync.service.ts index a6ed7281851..ce776f53685 100644 --- a/apps/browser/src/platform/sync/foreground-sync.service.ts +++ b/apps/browser/src/platform/sync/foreground-sync.service.ts @@ -14,6 +14,7 @@ import { import { Utils } from "@bitwarden/common/platform/misc/utils"; import { StateProvider } from "@bitwarden/common/platform/state"; import { CoreSyncService } from "@bitwarden/common/platform/sync/internal"; +import { SyncOptions } from "@bitwarden/common/platform/sync/sync.service"; import { SendApiService } from "@bitwarden/common/tools/send/services/send-api.service.abstraction"; import { InternalSendService } from "@bitwarden/common/tools/send/services/send.service.abstraction"; import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service"; @@ -22,7 +23,7 @@ import { InternalFolderService } from "@bitwarden/common/vault/abstractions/fold import { FULL_SYNC_FINISHED } from "./sync-service.listener"; -export type FullSyncMessage = { forceSync: boolean; allowThrowOnError: boolean; requestId: string }; +export type FullSyncMessage = { forceSync: boolean; options: SyncOptions; requestId: string }; export const DO_FULL_SYNC = new CommandDefinition("doFullSync"); @@ -60,9 +61,20 @@ export class ForegroundSyncService extends CoreSyncService { ); } - async fullSync(forceSync: boolean, allowThrowOnError: boolean = false): Promise { + async fullSync( + forceSync: boolean, + allowThrowOnErrorOrOptions?: boolean | SyncOptions, + ): Promise { this.syncInProgress = true; try { + // Normalize options + const options = + typeof allowThrowOnErrorOrOptions === "boolean" + ? { allowThrowOnError: allowThrowOnErrorOrOptions, skipTokenRefresh: false } + : { + allowThrowOnError: allowThrowOnErrorOrOptions?.allowThrowOnError ?? false, + skipTokenRefresh: allowThrowOnErrorOrOptions?.skipTokenRefresh ?? false, + }; const requestId = Utils.newGuid(); const syncCompletedPromise = firstValueFrom( this.messageListener.messages$(FULL_SYNC_FINISHED).pipe( @@ -79,10 +91,10 @@ export class ForegroundSyncService extends CoreSyncService { }), ), ); - this.messageSender.send(DO_FULL_SYNC, { forceSync, allowThrowOnError, requestId }); + this.messageSender.send(DO_FULL_SYNC, { forceSync, options, requestId }); const result = await syncCompletedPromise; - if (allowThrowOnError && result.errorMessage != null) { + if (options.allowThrowOnError && result.errorMessage != null) { throw new Error(result.errorMessage); } diff --git a/apps/browser/src/platform/sync/sync-service.listener.spec.ts b/apps/browser/src/platform/sync/sync-service.listener.spec.ts index 51f97e9f879..9682e2cdb57 100644 --- a/apps/browser/src/platform/sync/sync-service.listener.spec.ts +++ b/apps/browser/src/platform/sync/sync-service.listener.spec.ts @@ -27,11 +27,18 @@ describe("SyncServiceListener", () => { const emissionPromise = firstValueFrom(listener); syncService.fullSync.mockResolvedValueOnce(value); - messages.next({ forceSync: true, allowThrowOnError: false, requestId: "1" }); + messages.next({ + forceSync: true, + options: { allowThrowOnError: false, skipTokenRefresh: false }, + requestId: "1", + }); await emissionPromise; - expect(syncService.fullSync).toHaveBeenCalledWith(true, false); + expect(syncService.fullSync).toHaveBeenCalledWith(true, { + allowThrowOnError: false, + skipTokenRefresh: false, + }); expect(messageSender.send).toHaveBeenCalledWith(FULL_SYNC_FINISHED, { successfully: value, errorMessage: null, @@ -45,11 +52,18 @@ describe("SyncServiceListener", () => { const emissionPromise = firstValueFrom(listener); syncService.fullSync.mockRejectedValueOnce(new Error("SyncError")); - messages.next({ forceSync: true, allowThrowOnError: false, requestId: "1" }); + messages.next({ + forceSync: true, + options: { allowThrowOnError: false, skipTokenRefresh: false }, + requestId: "1", + }); await emissionPromise; - expect(syncService.fullSync).toHaveBeenCalledWith(true, false); + expect(syncService.fullSync).toHaveBeenCalledWith(true, { + allowThrowOnError: false, + skipTokenRefresh: false, + }); expect(messageSender.send).toHaveBeenCalledWith(FULL_SYNC_FINISHED, { successfully: false, errorMessage: "SyncError", diff --git a/apps/browser/src/platform/sync/sync-service.listener.ts b/apps/browser/src/platform/sync/sync-service.listener.ts index b7171528648..4274eafcf6a 100644 --- a/apps/browser/src/platform/sync/sync-service.listener.ts +++ b/apps/browser/src/platform/sync/sync-service.listener.ts @@ -9,6 +9,7 @@ import { MessageSender, isExternalMessage, } from "@bitwarden/common/platform/messaging"; +import { SyncOptions } from "@bitwarden/common/platform/sync/sync.service"; import { SyncService } from "@bitwarden/common/vault/abstractions/sync/sync.service.abstraction"; import { DO_FULL_SYNC } from "./foreground-sync.service"; @@ -34,15 +35,15 @@ export class SyncServiceListener { listener$(): Observable { return this.messageListener.messages$(DO_FULL_SYNC).pipe( filter((message) => isExternalMessage(message)), - concatMap(async ({ forceSync, allowThrowOnError, requestId }) => { - await this.doFullSync(forceSync, allowThrowOnError, requestId); + concatMap(async ({ forceSync, options, requestId }) => { + await this.doFullSync(forceSync, options, requestId); }), ); } - private async doFullSync(forceSync: boolean, allowThrowOnError: boolean, requestId: string) { + private async doFullSync(forceSync: boolean, options: SyncOptions, requestId: string) { try { - const result = await this.syncService.fullSync(forceSync, allowThrowOnError); + const result = await this.syncService.fullSync(forceSync, options); this.messageSender.send(FULL_SYNC_FINISHED, { successfully: result, errorMessage: null, diff --git a/apps/browser/src/popup/services/services.module.ts b/apps/browser/src/popup/services/services.module.ts index dad4e887a12..00b8ae81cf9 100644 --- a/apps/browser/src/popup/services/services.module.ts +++ b/apps/browser/src/popup/services/services.module.ts @@ -557,7 +557,7 @@ const safeProviders: SafeProvider[] = [ safeProvider({ provide: TwoFactorAuthEmailComponentService, useClass: ExtensionTwoFactorAuthEmailComponentService, - deps: [DialogService, WINDOW], + deps: [DialogService, WINDOW, ConfigService], }), safeProvider({ provide: TwoFactorAuthWebAuthnComponentService, diff --git a/apps/browser/src/popup/tabs-v2.component.html b/apps/browser/src/popup/tabs-v2.component.html new file mode 100644 index 00000000000..bde3aaa3d31 --- /dev/null +++ b/apps/browser/src/popup/tabs-v2.component.html @@ -0,0 +1,3 @@ + + + diff --git a/apps/browser/src/popup/tabs-v2.component.ts b/apps/browser/src/popup/tabs-v2.component.ts index 4cdb8fc029d..24ce9d8cb12 100644 --- a/apps/browser/src/popup/tabs-v2.component.ts +++ b/apps/browser/src/popup/tabs-v2.component.ts @@ -1,11 +1,53 @@ import { Component } from "@angular/core"; +import { combineLatest, map } from "rxjs"; + +import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum"; +import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; +import { HasNudgeService } from "@bitwarden/vault"; @Component({ selector: "app-tabs-v2", - template: ` - - - - `, + templateUrl: "./tabs-v2.component.html", + providers: [HasNudgeService], }) -export class TabsV2Component {} +export class TabsV2Component { + constructor( + private readonly hasNudgeService: HasNudgeService, + private readonly configService: ConfigService, + ) {} + + protected navButtons$ = combineLatest([ + this.configService.getFeatureFlag$(FeatureFlag.PM8851_BrowserOnboardingNudge), + this.hasNudgeService.nudgeStatus$(), + ]).pipe( + map(([onboardingFeatureEnabled, nudgeStatus]) => { + return [ + { + label: "vault", + page: "/tabs/vault", + iconKey: "lock", + iconKeyActive: "lock-f", + }, + { + label: "generator", + page: "/tabs/generator", + iconKey: "generate", + iconKeyActive: "generate-f", + }, + { + label: "send", + page: "/tabs/send", + iconKey: "send", + iconKeyActive: "send-f", + }, + { + label: "settings", + page: "/tabs/settings", + iconKey: "cog", + iconKeyActive: "cog-f", + showBerry: onboardingFeatureEnabled && !nudgeStatus.hasSpotlightDismissed, + }, + ]; + }), + ); +} diff --git a/apps/browser/src/safari/desktop/Info.plist b/apps/browser/src/safari/desktop/Info.plist index 69ea518a0ae..b687d9d2f3a 100644 --- a/apps/browser/src/safari/desktop/Info.plist +++ b/apps/browser/src/safari/desktop/Info.plist @@ -25,7 +25,7 @@ LSMinimumSystemVersion $(MACOSX_DEPLOYMENT_TARGET) NSHumanReadableCopyright - Copyright © 2015-2024 Bitwarden Inc. All rights reserved. + Copyright © 2015-2025 Bitwarden Inc. All rights reserved. NSMainStoryboardFile Main NSPrincipalClass diff --git a/apps/browser/src/safari/safari/Info.plist b/apps/browser/src/safari/safari/Info.plist index b79ed132ea9..95172846758 100644 --- a/apps/browser/src/safari/safari/Info.plist +++ b/apps/browser/src/safari/safari/Info.plist @@ -30,7 +30,7 @@ $(PRODUCT_MODULE_NAME).SafariWebExtensionHandler NSHumanReadableCopyright - Copyright © 2015-2024 Bitwarden Inc. All rights reserved. + Copyright © 2015-2025 Bitwarden Inc. All rights reserved. NSHumanReadableDescription A secure and free password manager for all of your devices. SFSafariAppExtensionBundleIdentifiersToReplace diff --git a/apps/browser/src/tools/popup/settings/settings-v2.component.html b/apps/browser/src/tools/popup/settings/settings-v2.component.html index 26aeea4f20a..b6f98b649fe 100644 --- a/apps/browser/src/tools/popup/settings/settings-v2.component.html +++ b/apps/browser/src/tools/popup/settings/settings-v2.component.html @@ -29,9 +29,26 @@ - + - {{ "vault" | i18n }} + + {{ "settingsVaultOptions" | i18n }} + + 1 + + diff --git a/apps/browser/src/tools/popup/settings/settings-v2.component.ts b/apps/browser/src/tools/popup/settings/settings-v2.component.ts index 5f3eb1c8f12..737d79ea4ca 100644 --- a/apps/browser/src/tools/popup/settings/settings-v2.component.ts +++ b/apps/browser/src/tools/popup/settings/settings-v2.component.ts @@ -1,9 +1,14 @@ import { CommonModule } from "@angular/common"; -import { Component } from "@angular/core"; +import { Component, OnInit } from "@angular/core"; import { RouterModule } from "@angular/router"; +import { firstValueFrom, Observable } from "rxjs"; import { JslibModule } from "@bitwarden/angular/jslib.module"; -import { ItemModule } from "@bitwarden/components"; +import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; +import { getUserId } from "@bitwarden/common/auth/services/account.service"; +import { UserId } from "@bitwarden/common/types/guid"; +import { BadgeComponent, ItemModule } from "@bitwarden/components"; +import { NudgeStatus, VaultNudgesService, VaultNudgeType } from "@bitwarden/vault"; import { CurrentAccountComponent } from "../../../auth/popup/account-switching/current-account.component"; import { PopOutComponent } from "../../../platform/popup/components/pop-out.component"; @@ -22,6 +27,29 @@ import { PopupPageComponent } from "../../../platform/popup/layout/popup-page.co PopOutComponent, ItemModule, CurrentAccountComponent, + BadgeComponent, ], }) -export class SettingsV2Component {} +export class SettingsV2Component implements OnInit { + VaultNudgeType = VaultNudgeType; + showVaultBadge$: Observable = new Observable(); + activeUserId: UserId | null = null; + + constructor( + private readonly vaultNudgesService: VaultNudgesService, + private readonly accountService: AccountService, + ) {} + async ngOnInit() { + this.activeUserId = await firstValueFrom(this.accountService.activeAccount$.pipe(getUserId)); + this.showVaultBadge$ = this.vaultNudgesService.showNudge$( + VaultNudgeType.EmptyVaultNudge, + this.activeUserId, + ); + } + + async dismissBadge(type: VaultNudgeType) { + if (!(await firstValueFrom(this.showVaultBadge$)).hasBadgeDismissed) { + await this.vaultNudgesService.dismissNudge(type, this.activeUserId as UserId, true); + } + } +} diff --git a/apps/browser/src/vault/popup/components/at-risk-callout/at-risk-password-callout.component.ts b/apps/browser/src/vault/popup/components/at-risk-callout/at-risk-password-callout.component.ts index fa4137d9849..ed78d9433f1 100644 --- a/apps/browser/src/vault/popup/components/at-risk-callout/at-risk-password-callout.component.ts +++ b/apps/browser/src/vault/popup/components/at-risk-callout/at-risk-password-callout.component.ts @@ -1,7 +1,7 @@ import { CommonModule } from "@angular/common"; import { Component, inject } from "@angular/core"; import { RouterModule } from "@angular/router"; -import { map, of, switchMap } from "rxjs"; +import { map, switchMap } from "rxjs"; import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; import { getUserId } from "@bitwarden/common/auth/services/account.service"; @@ -20,21 +20,7 @@ export class AtRiskPasswordCalloutComponent { private activeAccount$ = inject(AccountService).activeAccount$.pipe(getUserId); protected pendingTasks$ = this.activeAccount$.pipe( - switchMap((userId) => - this.taskService.tasksEnabled$(userId).pipe( - switchMap((enabled) => { - if (!enabled) { - return of([]); - } - return this.taskService - .pendingTasks$(userId) - .pipe( - map((tasks) => - tasks.filter((t) => t.type === SecurityTaskType.UpdateAtRiskCredential), - ), - ); - }), - ), - ), + switchMap((userId) => this.taskService.pendingTasks$(userId)), + map((tasks) => tasks.filter((t) => t.type === SecurityTaskType.UpdateAtRiskCredential)), ); } diff --git a/apps/browser/src/vault/popup/components/at-risk-passwords/at-risk-passwords.component.spec.ts b/apps/browser/src/vault/popup/components/at-risk-passwords/at-risk-passwords.component.spec.ts index 25bf3ce3716..ff583061684 100644 --- a/apps/browser/src/vault/popup/components/at-risk-passwords/at-risk-passwords.component.spec.ts +++ b/apps/browser/src/vault/popup/components/at-risk-passwords/at-risk-passwords.component.spec.ts @@ -12,10 +12,13 @@ import { AccountService } from "@bitwarden/common/auth/abstractions/account.serv import { AutofillOverlayVisibility } from "@bitwarden/common/autofill/constants"; import { AutofillSettingsServiceAbstraction } from "@bitwarden/common/autofill/services/autofill-settings.service"; import { InlineMenuVisibilitySetting } from "@bitwarden/common/autofill/types"; +import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service"; import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view"; +import { EndUserNotificationService } from "@bitwarden/common/vault/notifications"; +import { NotificationView } from "@bitwarden/common/vault/notifications/models"; import { SecurityTask, SecurityTaskType, TaskService } from "@bitwarden/common/vault/tasks"; import { DialogService, ToastService } from "@bitwarden/components"; import { @@ -66,6 +69,7 @@ describe("AtRiskPasswordsComponent", () => { let mockTasks$: BehaviorSubject; let mockCiphers$: BehaviorSubject; let mockOrgs$: BehaviorSubject; + let mockNotifications$: BehaviorSubject; let mockInlineMenuVisibility$: BehaviorSubject; let calloutDismissed$: BehaviorSubject; const setInlineMenuVisibility = jest.fn(); @@ -73,6 +77,7 @@ describe("AtRiskPasswordsComponent", () => { const mockAtRiskPasswordPageService = mock(); const mockChangeLoginPasswordService = mock(); const mockDialogService = mock(); + const mockConfigService = mock(); beforeEach(async () => { mockTasks$ = new BehaviorSubject([ @@ -101,6 +106,7 @@ describe("AtRiskPasswordsComponent", () => { name: "Org 1", } as Organization, ]); + mockNotifications$ = new BehaviorSubject([]); mockInlineMenuVisibility$ = new BehaviorSubject( AutofillOverlayVisibility.Off, @@ -110,6 +116,7 @@ describe("AtRiskPasswordsComponent", () => { setInlineMenuVisibility.mockClear(); mockToastService.showToast.mockClear(); mockDialogService.open.mockClear(); + mockConfigService.getFeatureFlag.mockClear(); mockAtRiskPasswordPageService.isCalloutDismissed.mockReturnValue(calloutDismissed$); await TestBed.configureTestingModule({ @@ -133,6 +140,12 @@ describe("AtRiskPasswordsComponent", () => { cipherViews$: () => mockCiphers$, }, }, + { + provide: EndUserNotificationService, + useValue: { + unreadNotifications$: () => mockNotifications$, + }, + }, { provide: I18nService, useValue: { t: (key: string) => key } }, { provide: AccountService, useValue: { activeAccount$: of({ id: "user" }) } }, { provide: PlatformUtilsService, useValue: mock() }, @@ -145,6 +158,7 @@ describe("AtRiskPasswordsComponent", () => { }, }, { provide: ToastService, useValue: mockToastService }, + { provide: ConfigService, useValue: mockConfigService }, ], }) .overrideModule(JslibModule, { diff --git a/apps/browser/src/vault/popup/components/at-risk-passwords/at-risk-passwords.component.ts b/apps/browser/src/vault/popup/components/at-risk-passwords/at-risk-passwords.component.ts index 37c445f6c30..1b43151193a 100644 --- a/apps/browser/src/vault/popup/components/at-risk-passwords/at-risk-passwords.component.ts +++ b/apps/browser/src/vault/popup/components/at-risk-passwords/at-risk-passwords.component.ts @@ -1,7 +1,19 @@ import { CommonModule } from "@angular/common"; -import { Component, inject, OnInit, signal } from "@angular/core"; +import { Component, DestroyRef, inject, OnInit, signal } from "@angular/core"; +import { takeUntilDestroyed } from "@angular/core/rxjs-interop"; import { Router } from "@angular/router"; -import { combineLatest, firstValueFrom, map, of, shareReplay, startWith, switchMap } from "rxjs"; +import { + combineLatest, + concat, + concatMap, + firstValueFrom, + map, + of, + shareReplay, + startWith, + switchMap, + take, +} from "rxjs"; import { JslibModule } from "@bitwarden/angular/jslib.module"; import { @@ -11,10 +23,13 @@ import { import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; import { AutofillOverlayVisibility } from "@bitwarden/common/autofill/constants"; import { AutofillSettingsServiceAbstraction } from "@bitwarden/common/autofill/services/autofill-settings.service"; +import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum"; +import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service"; import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view"; +import { EndUserNotificationService } from "@bitwarden/common/vault/notifications"; import { SecurityTaskType, TaskService } from "@bitwarden/common/vault/tasks"; import { filterOutNullish } from "@bitwarden/common/vault/utils/observable-utilities"; import { @@ -81,6 +96,9 @@ export class AtRiskPasswordsComponent implements OnInit { private changeLoginPasswordService = inject(ChangeLoginPasswordService); private platformUtilsService = inject(PlatformUtilsService); private dialogService = inject(DialogService); + private endUserNotificationService = inject(EndUserNotificationService); + private configService = inject(ConfigService); + private destroyRef = inject(DestroyRef); /** * The cipher that is currently being launched. Used to show a loading spinner on the badge button. @@ -180,6 +198,36 @@ export class AtRiskPasswordsComponent implements OnInit { await this.atRiskPasswordPageService.dismissGettingStarted(userId); } } + + if (await this.configService.getFeatureFlag(FeatureFlag.EndUserNotifications)) { + this.markTaskNotificationsAsRead(); + } + } + + private markTaskNotificationsAsRead() { + this.activeUserData$ + .pipe( + switchMap(({ tasks, userId }) => { + return this.endUserNotificationService.unreadNotifications$(userId).pipe( + take(1), + map((notifications) => { + return notifications.filter((notification) => { + return tasks.some((task) => task.id === notification.taskId); + }); + }), + concatMap((unreadTaskNotifications) => { + // TODO: Investigate creating a bulk endpoint to mark notifications as read + return concat( + ...unreadTaskNotifications.map((n) => + this.endUserNotificationService.markAsRead(n.id, userId), + ), + ); + }), + ); + }), + takeUntilDestroyed(this.destroyRef), + ) + .subscribe(); } async viewCipher(cipher: CipherView) { diff --git a/apps/browser/src/vault/popup/components/vault-v2/attachments/attachments-v2.component.spec.ts b/apps/browser/src/vault/popup/components/vault-v2/attachments/attachments-v2.component.spec.ts index 29793a41ec9..7c2cc99e300 100644 --- a/apps/browser/src/vault/popup/components/vault-v2/attachments/attachments-v2.component.spec.ts +++ b/apps/browser/src/vault/popup/components/vault-v2/attachments/attachments-v2.component.spec.ts @@ -5,6 +5,8 @@ import { ActivatedRoute, Router } from "@angular/router"; import { mock } from "jest-mock-extended"; import { BehaviorSubject } from "rxjs"; +import { ApiService } from "@bitwarden/common/abstractions/api.service"; +import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction"; import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; @@ -77,6 +79,8 @@ describe("AttachmentsV2Component", () => { provide: AccountService, useValue: accountService, }, + { provide: ApiService, useValue: mock() }, + { provide: OrganizationService, useValue: mock() }, ], }) .overrideComponent(AttachmentsV2Component, { diff --git a/apps/browser/src/vault/popup/components/vault-v2/new-item-dropdown/new-item-dropdown-v2.component.html b/apps/browser/src/vault/popup/components/vault-v2/new-item-dropdown/new-item-dropdown-v2.component.html index c952260a9a9..af627e22ef2 100644 --- a/apps/browser/src/vault/popup/components/vault-v2/new-item-dropdown/new-item-dropdown-v2.component.html +++ b/apps/browser/src/vault/popup/components/vault-v2/new-item-dropdown/new-item-dropdown-v2.component.html @@ -1,4 +1,4 @@ - + {{ "new" | i18n }} diff --git a/apps/browser/src/vault/popup/components/vault-v2/vault-v2.component.html b/apps/browser/src/vault/popup/components/vault-v2/vault-v2.component.html index 2a50eb43960..894f27245b2 100644 --- a/apps/browser/src/vault/popup/components/vault-v2/vault-v2.component.html +++ b/apps/browser/src/vault/popup/components/vault-v2/vault-v2.component.html @@ -14,11 +14,12 @@ > {{ "yourVaultIsEmpty" | i18n }} - {{ "autofillSuggestionsTip" | i18n }} - + + {{ "emptyVaultDescription" | i18n }} + + + {{ "newLogin" | i18n }} + @@ -28,11 +29,31 @@ > - - - + + + + + + + + + + + + + diff --git a/apps/browser/src/vault/popup/components/vault-v2/vault-v2.component.ts b/apps/browser/src/vault/popup/components/vault-v2/vault-v2.component.ts index 7f5242dcf18..64805a02394 100644 --- a/apps/browser/src/vault/popup/components/vault-v2/vault-v2.component.ts +++ b/apps/browser/src/vault/popup/components/vault-v2/vault-v2.component.ts @@ -2,30 +2,38 @@ import { CdkVirtualScrollableElement, ScrollingModule } from "@angular/cdk/scrol import { CommonModule } from "@angular/common"; import { AfterViewInit, Component, DestroyRef, OnDestroy, OnInit, ViewChild } from "@angular/core"; import { takeUntilDestroyed } from "@angular/core/rxjs-interop"; +import { Router, RouterModule } from "@angular/router"; import { combineLatest, filter, - map, firstValueFrom, + map, Observable, shareReplay, + startWith, switchMap, take, - startWith, } 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 { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum"; -import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; -import { CipherId, CollectionId, OrganizationId } from "@bitwarden/common/types/guid"; +import { CipherId, CollectionId, OrganizationId, UserId } from "@bitwarden/common/types/guid"; import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service"; import { CipherType } from "@bitwarden/common/vault/enums"; import { ButtonModule, DialogService, Icons, NoItemsModule } from "@bitwarden/components"; -import { DecryptionFailureDialogComponent, VaultIcons } from "@bitwarden/vault"; +import { + DecryptionFailureDialogComponent, + SpotlightComponent, + VaultIcons, + VaultNudgesService, + VaultNudgeType, +} from "@bitwarden/vault"; import { CurrentAccountComponent } from "../../../../auth/popup/account-switching/current-account.component"; +import { BrowserApi } from "../../../../platform/browser/browser-api"; +import BrowserPopupUtils from "../../../../platform/popup/browser-popup-utils"; import { PopOutComponent } from "../../../../platform/popup/components/pop-out.component"; import { PopupHeaderComponent } from "../../../../platform/popup/layout/popup-header.component"; import { PopupPageComponent } from "../../../../platform/popup/layout/popup-page.component"; @@ -74,14 +82,29 @@ enum VaultState { VaultHeaderV2Component, AtRiskPasswordCalloutComponent, NewSettingsCalloutComponent, + SpotlightComponent, + RouterModule, ], providers: [VaultPageService], }) export class VaultV2Component implements OnInit, AfterViewInit, OnDestroy { @ViewChild(CdkVirtualScrollableElement) virtualScrollElement?: CdkVirtualScrollableElement; + VaultNudgeType = VaultNudgeType; cipherType = CipherType; + private activeUserId$ = this.accountService.activeAccount$.pipe(getUserId); + showEmptyVaultSpotlight$: Observable = this.activeUserId$.pipe( + switchMap((userId) => + this.vaultNudgesService.showNudge$(VaultNudgeType.EmptyVaultNudge, userId), + ), + map((nudgeStatus) => !nudgeStatus.hasSpotlightDismissed), + ); + showHasItemsVaultSpotlight$: Observable = this.activeUserId$.pipe( + switchMap((userId) => this.vaultNudgesService.showNudge$(VaultNudgeType.HasVaultItems, userId)), + map((nudgeStatus) => !nudgeStatus.hasSpotlightDismissed), + ); + activeUserId: UserId | null = null; protected favoriteCiphers$ = this.vaultPopupItemsService.favoriteCiphers$; protected remainingCiphers$ = this.vaultPopupItemsService.remainingCiphers$; protected allFilters$ = this.vaultPopupListFiltersService.allFilters$; @@ -131,7 +154,8 @@ export class VaultV2Component implements OnInit, AfterViewInit, OnDestroy { private dialogService: DialogService, private vaultCopyButtonsService: VaultPopupCopyButtonsService, private introCarouselService: IntroCarouselService, - private configService: ConfigService, + private vaultNudgesService: VaultNudgesService, + private router: Router, ) { combineLatest([ this.vaultPopupItemsService.emptyVault$, @@ -169,16 +193,12 @@ export class VaultV2Component implements OnInit, AfterViewInit, OnDestroy { } async ngOnInit() { - const hasVaultNudgeFlag = await this.configService.getFeatureFlag( - FeatureFlag.PM8851_BrowserOnboardingNudge, - ); - if (hasVaultNudgeFlag) { - await this.introCarouselService.setIntroCarouselDismissed(); - } - const activeUserId = await firstValueFrom(this.accountService.activeAccount$.pipe(getUserId)); + this.activeUserId = await firstValueFrom(this.accountService.activeAccount$.pipe(getUserId)); + + await this.introCarouselService.setIntroCarouselDismissed(); this.cipherService - .failedToDecryptCiphers$(activeUserId) + .failedToDecryptCiphers$(this.activeUserId) .pipe( map((ciphers) => (ciphers ? ciphers.filter((c) => !c.isDeleted) : [])), filter((ciphers) => ciphers.length > 0), @@ -196,5 +216,16 @@ export class VaultV2Component implements OnInit, AfterViewInit, OnDestroy { this.vaultScrollPositionService.stop(); } + async navigateToImport() { + await this.router.navigate(["/import"]); + if (await BrowserApi.isPopupOpen()) { + await BrowserPopupUtils.openCurrentPagePopout(window); + } + } + + async dismissVaultNudgeSpotlight(type: VaultNudgeType) { + await this.vaultNudgesService.dismissNudge(type, this.activeUserId as UserId); + } + protected readonly FeatureFlag = FeatureFlag; } diff --git a/apps/browser/src/vault/popup/services/browser-view-password-history.service.ts b/apps/browser/src/vault/popup/services/browser-view-password-history.service.ts index 5e400da9de5..ae6369d06a5 100644 --- a/apps/browser/src/vault/popup/services/browser-view-password-history.service.ts +++ b/apps/browser/src/vault/popup/services/browser-view-password-history.service.ts @@ -1,5 +1,3 @@ -// FIXME: Update this file to be type safe and remove this and next line -// @ts-strict-ignore import { inject } from "@angular/core"; import { Router } from "@angular/router"; diff --git a/apps/browser/src/vault/popup/services/intro-carousel.service.ts b/apps/browser/src/vault/popup/services/intro-carousel.service.ts index 2c523c5a93c..7d2bb7dedb9 100644 --- a/apps/browser/src/vault/popup/services/intro-carousel.service.ts +++ b/apps/browser/src/vault/popup/services/intro-carousel.service.ts @@ -1,6 +1,8 @@ import { Injectable } from "@angular/core"; -import { map, Observable } from "rxjs"; +import { firstValueFrom, map, Observable } from "rxjs"; +import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum"; +import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; import { GlobalState, KeyDefinition, @@ -26,9 +28,17 @@ export class IntroCarouselService { map((x) => x ?? false), ); - constructor(private stateProvider: StateProvider) {} + constructor( + private stateProvider: StateProvider, + private configService: ConfigService, + ) {} async setIntroCarouselDismissed(): Promise { - await this.introCarouselState.update(() => true); + const hasVaultNudgeFlag = await firstValueFrom( + this.configService.getFeatureFlag$(FeatureFlag.PM8851_BrowserOnboardingNudge), + ); + if (hasVaultNudgeFlag) { + await this.introCarouselState.update(() => true); + } } } 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 c16923e28fc..e14f74d45d7 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 @@ -52,6 +52,7 @@ export class VaultPopupItemsService { private cachedSearchText = inject(PopupViewCacheService).signal({ key: "vault-search-text", initialValue: "", + persistNavigation: true, }); readonly searchText$ = toObservable(this.cachedSearchText); diff --git a/apps/browser/src/vault/popup/services/vault-popup-list-filters.service.ts b/apps/browser/src/vault/popup/services/vault-popup-list-filters.service.ts index 6cce5796cbe..f11fa0f63f0 100644 --- a/apps/browser/src/vault/popup/services/vault-popup-list-filters.service.ts +++ b/apps/browser/src/vault/popup/services/vault-popup-list-filters.service.ts @@ -188,6 +188,7 @@ export class VaultPopupListFiltersService { key: "vault-filters", initialValue: {}, deserializer: (v) => v, + persistNavigation: true, }); this.deserializeFilters(cachedFilters()); diff --git a/apps/browser/src/vault/popup/settings/folders-v2.component.html b/apps/browser/src/vault/popup/settings/folders-v2.component.html index 35a0fbec0a9..552547c0230 100644 --- a/apps/browser/src/vault/popup/settings/folders-v2.component.html +++ b/apps/browser/src/vault/popup/settings/folders-v2.component.html @@ -1,7 +1,13 @@ - + {{ "new" | i18n }} diff --git a/apps/browser/store/locales/ar/copy.resx b/apps/browser/store/locales/ar/copy.resx index 9fdfb942100..a83bafbf1ae 100644 --- a/apps/browser/store/locales/ar/copy.resx +++ b/apps/browser/store/locales/ar/copy.resx @@ -118,58 +118,60 @@ System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - Bitwarden Password Manager + مدير كلمات المرور بتواردن - At home, at work, or on the go, Bitwarden easily secures all your passwords, passkeys, and sensitive information. + في المنزل، في العمل، أو في أثناء التنقل، يقوم بتواردن بتأمين جميع كلمات المرور والمعلومات الحساسة بسهولة. - Recognized as the best password manager by PCMag, WIRED, The Verge, CNET, G2, and more! + مُعترف به كأفضل مدير كلمات مرور من قِبل PCMag وWIRED وThe Verge وCNET وG2 وغيرها! -SECURE YOUR DIGITAL LIFE -Secure your digital life and protect against data breaches by generating and saving unique, strong passwords for every account. Maintain everything in an end-to-end encrypted password vault that only you can access. +أمّن حياتك الرقمية -ACCESS YOUR DATA, ANYWHERE, ANYTIME, ON ANY DEVICE -Easily manage, store, secure, and share unlimited passwords across unlimited devices without restrictions. +أمّن حياتك الرقمية واحمِ بياناتك من الاختراقات بإنشاء كلمات مرور فريدة وقوية وحفظها لكل حساب. احفظ كل شيء في مخزن كلمات مرور مشفّر من البداية إلى النهاية، لا يمكن لأحد الوصول إليه سواك. -EVERYONE SHOULD HAVE THE TOOLS TO STAY SAFE ONLINE -Utilize Bitwarden for free with no ads or selling data. Bitwarden believes everyone should have the ability to stay safe online. Premium plans offer access to advanced features. +الوصول إلى بياناتك، من أي مكان، وفي أي وقت، وعلى أي جهاز -EMPOWER YOUR TEAMS WITH BITWARDEN -Plans for Teams and Enterprise come with professional business features. Some examples include SSO integration, self-hosting, directory integration and SCIM provisioning, global policies, API access, event logs, and more. +يمكنك بسهولة إدارة كلمات مرور غير محدودة وتخزينها وتأمينها ومشاركتها عبر عدد غير محدود من الأجهزة دون قيود. -Use Bitwarden to secure your workforce and share sensitive information with colleagues. +يجب أن يمتلك الجميع الأدوات اللازمة للبقاء آمنًا على الإنترنت +استخدم بيتواردن مجانًا دون إعلانات أو بيع بيانات. تؤمن بيتواردن بحق الجميع في البقاء آمنًا على الإنترنت. توفر الباقات المميزة إمكانية الوصول إلى ميزات متقدمة. -More reasons to choose Bitwarden: +عزز قدرات فرقك مع بتواردن +تأتي باقاتنا للفرق والمؤسسات مزودة بميزات احترافية للأعمال. من الأمثلة على ذلك تكامل SSO، والاستضافة الذاتية، وتكامل الدليل، وتوفير SCIM، والسياسات العالمية، والوصول إلى واجهة برمجة التطبيقات، وسجلات الأحداث، والمزيد. -World-Class Encryption -Passwords are protected with advanced end-to-end encryption (AES-256 bit, salted hashtag, and PBKDF2 SHA-256) so your data stays secure and private. +استخدم بيتواردن لتأمين فريق عملك ومشاركة المعلومات الحساسة مع زملائك. -3rd-party Audits -Bitwarden regularly conducts comprehensive third-party security audits with notable security firms. These annual audits include source code assessments and penetration testing across Bitwarden IPs, servers, and web applications. +أسباب إضافية لاختيار بتواردن: -Advanced 2FA -Secure your login with a third-party authenticator, emailed codes, or FIDO2 WebAuthn credentials such as a hardware security key or passkey. +تشفير عالمي المستوى +كلمات المرور محمية بتشفير متقدم من البداية إلى النهاية (AES-256 بت، وهاشتاج مُملح، وPBKDF2 SHA-256) لضمان أمان بياناتك وخصوصيتها. -Bitwarden Send -Transmit data directly to others while maintaining end-to-end encrypted security and limiting exposure. +عمليات تدقيق خارجية +تُجري بيتواردن بانتظام عمليات تدقيق أمنية شاملة من جهات خارجية بالتعاون مع شركات أمنية مرموقة. تشمل هذه العمليات السنوية تقييمات لشفرة المصدر واختبارات اختراق عبر عناوين IP وخوادم وتطبيقات الويب الخاصة بـبتواردن. -Built-in Generator -Create long, complex, and distinct passwords and unique usernames for every site you visit. Integrate with email alias providers for additional privacy. +مصادقة ثنائية متقدمة +أمّن تسجيل دخولك باستخدام مُصادق خارجي، أو رموز مُرسلة عبر البريد الإلكتروني، أو بيانات اعتماد FIDO2 WebAuthn مثل مفتاح أمان الأجهزة أو كلمة المرور. -Global Translations -Bitwarden translations exist for more than 60 languages, translated by the global community though Crowdin. +إرسال بتواردن +انقل البيانات مباشرةً إلى الآخرين مع الحفاظ على أمان مشفّر من البداية إلى النهاية والحد من التعرض. -Cross-Platform Applications -Secure and share sensitive data within your Bitwarden Vault from any browser, mobile device, or desktop OS, and more. +مولد مدمج +أنشئ كلمات مرور طويلة ومعقدة ومميزة وأسماء مستخدمين فريدة لكل موقع تزوره. تكامل مع مزودي أسماء البريد الإلكتروني المستعارة لمزيد من الخصوصية. -Bitwarden secures more than just passwords -End-to-end encrypted credential management solutions from Bitwarden empower organizations to secure everything, including developer secrets and passkey experiences. Visit Bitwarden.com to learn more about Bitwarden Secrets Manager and Bitwarden Passwordless.dev! +ترجمات عالمية +تتوفر ترجمات بتواردن لأكثر من 60 لغة، مترجمة من قِبل المجتمع العالمي عبر Crowdin. + +تطبيقات متعددة المنصات +أمّن بياناتك الحساسة وشاركها داخل مخزن بتواردن من أي متصفح أو جهاز محمول أو نظام تشغيل سطح مكتب، وغير ذلك الكثير. + +يؤمن بتواردن أكثر من مجرد كلمات مرور +تُمكّن حلول إدارة بيانات الاعتماد المشفرة من البداية إلى النهاية من بتواردن المؤسسات من تأمين كل شيء، بما في ذلك أسرار المطورين وتجارب مفاتيح المرور. تفضل بزيارة Bitwarden.com لمعرفة المزيد عن المدير السري لبتواردن وBitwarden Passwordless.dev! - At home, at work, or on the go, Bitwarden easily secures all your passwords, passkeys, and sensitive information. + في المنزل، في العمل، أو في أثناء التنقل، يقوم بتواردن بتأمين جميع كلمات المرور والمعلومات الحساسة بسهولة. مزامنة خزانتك والوصول إليها من عدة أجهزة diff --git a/apps/browser/webpack.config.js b/apps/browser/webpack.config.js index 4b66ed7d70a..6d9113be7ed 100644 --- a/apps/browser/webpack.config.js +++ b/apps/browser/webpack.config.js @@ -81,6 +81,8 @@ const moduleRules = [ loader: "babel-loader", options: { configFile: "../../babel.config.json", + cacheDirectory: ENV === "development", + compact: ENV !== "development", }, }, ], @@ -205,6 +207,20 @@ const mainConfig = { "content/send-on-installed-message": "./src/vault/content/send-on-installed-message.ts", "content/send-popup-open-message": "./src/vault/content/send-popup-open-message.ts", }, + cache: + ENV !== "development" + ? false + : { + type: "filesystem", + name: "main-cache", + cacheDirectory: path.resolve(__dirname, "../../node_modules/.cache/webpack-browser-main"), + buildDependencies: { + config: [__filename], + }, + }, + snapshot: { + unmanagedPaths: [path.resolve(__dirname, "../../node_modules/@bitwarden/")], + }, optimization: { minimize: ENV !== "development", minimizer: [ @@ -263,6 +279,7 @@ const mainConfig = { fs: false, path: require.resolve("path-browserify"), }, + cache: true, }, output: { filename: "[name].js", @@ -357,6 +374,23 @@ if (manifestVersion == 2) { ], noParse: /argon2(-simd)?\.wasm$/, }, + cache: + ENV !== "development" + ? false + : { + type: "filesystem", + name: "background-cache", + cacheDirectory: path.resolve( + __dirname, + "../../node_modules/.cache/webpack-browser-background", + ), + buildDependencies: { + config: [__filename], + }, + }, + snapshot: { + unmanagedPaths: [path.resolve(__dirname, "../../node_modules/@bitwarden/")], + }, experiments: { asyncWebAssembly: true, }, @@ -369,6 +403,7 @@ if (manifestVersion == 2) { fs: false, path: require.resolve("path-browserify"), }, + cache: true, }, dependencies: ["main"], plugins: [...requiredPlugins], diff --git a/apps/cli/README.md b/apps/cli/README.md index d39c0e39c8f..2b13270cdba 100644 --- a/apps/cli/README.md +++ b/apps/cli/README.md @@ -1,4 +1,4 @@ -[](https://github.com/bitwarden/clients/actions/workflows/build-cli.yml?query=branch:master) +[](https://github.com/bitwarden/clients/actions/workflows/build-cli.yml?query=branch:main) [](https://gitter.im/bitwarden/Lobby) # Bitwarden Command-line Interface diff --git a/apps/cli/package.json b/apps/cli/package.json index 04fe4290d31..79d4786a23c 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": "2025.3.0", + "version": "2025.4.0", "keywords": [ "bitwarden", "password", @@ -74,7 +74,7 @@ "form-data": "4.0.1", "https-proxy-agent": "7.0.6", "inquirer": "8.2.6", - "jsdom": "26.0.0", + "jsdom": "26.1.0", "jszip": "3.10.1", "koa": "2.16.1", "koa-bodyparser": "4.4.1", @@ -88,7 +88,7 @@ "papaparse": "5.5.2", "proper-lockfile": "4.1.2", "rxjs": "7.8.1", - "tldts": "6.1.77", + "tldts": "7.0.1", "zxcvbn": "4.4.2" } } diff --git a/apps/cli/src/admin-console/commands/confirm.command.ts b/apps/cli/src/admin-console/commands/confirm.command.ts index 0d5c7ba069c..1c900511499 100644 --- a/apps/cli/src/admin-console/commands/confirm.command.ts +++ b/apps/cli/src/admin-console/commands/confirm.command.ts @@ -57,7 +57,7 @@ export class ConfirmCommand { } const publicKeyResponse = await this.apiService.getUserPublicKey(orgUser.userId); const publicKey = Utils.fromB64ToArray(publicKeyResponse.publicKey); - const key = await this.encryptService.rsaEncrypt(orgKey.key, publicKey); + const key = await this.encryptService.encapsulateKeyUnsigned(orgKey, publicKey); const req = new OrganizationUserConfirmRequest(); req.key = key.encryptedString; await this.organizationUserApiService.postOrganizationUserConfirm( diff --git a/apps/cli/src/auth/commands/login.command.ts b/apps/cli/src/auth/commands/login.command.ts index 8d66a566038..8a94cc4175a 100644 --- a/apps/cli/src/auth/commands/login.command.ts +++ b/apps/cli/src/auth/commands/login.command.ts @@ -5,7 +5,7 @@ import * as http from "http"; import { OptionValues } from "commander"; import * as inquirer from "inquirer"; import Separator from "inquirer/lib/objects/separator"; -import { firstValueFrom, map, switchMap } from "rxjs"; +import { firstValueFrom, map } from "rxjs"; import { LoginStrategyServiceAbstraction, @@ -29,7 +29,6 @@ import { TokenTwoFactorRequest } from "@bitwarden/common/auth/models/request/ide import { PasswordRequest } from "@bitwarden/common/auth/models/request/password.request"; import { TwoFactorEmailRequest } from "@bitwarden/common/auth/models/request/two-factor-email.request"; import { UpdateTempPasswordRequest } from "@bitwarden/common/auth/models/request/update-temp-password.request"; -import { getUserId } from "@bitwarden/common/auth/services/account.service"; import { ClientType } from "@bitwarden/common/enums"; import { CryptoFunctionService } from "@bitwarden/common/key-management/crypto/abstractions/crypto-function.service"; import { KeyConnectorService } from "@bitwarden/common/key-management/key-connector/abstractions/key-connector.service"; @@ -40,6 +39,7 @@ import { Utils } from "@bitwarden/common/platform/misc/utils"; import { EncString } from "@bitwarden/common/platform/models/domain/enc-string"; import { SymmetricCryptoKey } from "@bitwarden/common/platform/models/domain/symmetric-crypto-key"; import { PasswordStrengthServiceAbstraction } from "@bitwarden/common/tools/password-strength"; +import { UserId } from "@bitwarden/common/types/guid"; import { SyncService } from "@bitwarden/common/vault/abstractions/sync/sync.service.abstraction"; import { PasswordGenerationServiceAbstraction } from "@bitwarden/generator-legacy"; import { KdfConfigService, KeyService } from "@bitwarden/key-management"; @@ -367,9 +367,9 @@ export class LoginCommand { clientSecret == null ) { if (response.forcePasswordReset === ForceSetPasswordReason.AdminForcePasswordReset) { - return await this.updateTempPassword(); + return await this.updateTempPassword(response.userId); } else if (response.forcePasswordReset === ForceSetPasswordReason.WeakMasterPassword) { - return await this.updateWeakPassword(password); + return await this.updateWeakPassword(response.userId, password); } } @@ -431,7 +431,7 @@ export class LoginCommand { return Response.success(res); } - private async updateWeakPassword(currentPassword: string) { + private async updateWeakPassword(userId: UserId, currentPassword: string) { // If no interaction available, alert user to use web vault if (!this.canInteract) { await this.logoutCallback(); @@ -448,6 +448,7 @@ export class LoginCommand { try { const { newPasswordHash, newUserKey, hint } = await this.collectNewMasterPasswordDetails( + userId, "Your master password does not meet one or more of your organization policies. In order to access the vault, you must update your master password now.", ); @@ -469,7 +470,7 @@ export class LoginCommand { } } - private async updateTempPassword() { + private async updateTempPassword(userId: UserId) { // If no interaction available, alert user to use web vault if (!this.canInteract) { await this.logoutCallback(); @@ -486,6 +487,7 @@ export class LoginCommand { try { const { newPasswordHash, newUserKey, hint } = await this.collectNewMasterPasswordDetails( + userId, "An organization administrator recently changed your master password. In order to access the vault, you must update your master password now.", ); @@ -510,10 +512,12 @@ export class LoginCommand { * Collect new master password and hint from the CLI. The collected password * is validated against any applicable master password policies, a new master * key is generated, and we use it to re-encrypt the user key + * @param userId - User ID of the account * @param prompt - Message that is displayed during the initial prompt * @param error */ private async collectNewMasterPasswordDetails( + userId: UserId, prompt: string, error?: string, ): Promise<{ @@ -539,11 +543,12 @@ export class LoginCommand { // Master Password Validation if (masterPassword == null || masterPassword === "") { - return this.collectNewMasterPasswordDetails(prompt, "Master password is required.\n"); + return this.collectNewMasterPasswordDetails(userId, prompt, "Master password is required.\n"); } if (masterPassword.length < Utils.minimumPasswordLength) { return this.collectNewMasterPasswordDetails( + userId, prompt, `Master password must be at least ${Utils.minimumPasswordLength} characters long.\n`, ); @@ -556,10 +561,7 @@ export class LoginCommand { ); const enforcedPolicyOptions = await firstValueFrom( - this.accountService.activeAccount$.pipe( - getUserId, - switchMap((userId) => this.policyService.masterPasswordPolicyOptions$(userId)), - ), + this.policyService.masterPasswordPolicyOptions$(userId), ); // Verify master password meets policy requirements @@ -572,6 +574,7 @@ export class LoginCommand { ) ) { return this.collectNewMasterPasswordDetails( + userId, prompt, "Your new master password does not meet the policy requirements.\n", ); @@ -589,6 +592,7 @@ export class LoginCommand { // Re-type Validation if (masterPassword !== masterPasswordRetype) { return this.collectNewMasterPasswordDetails( + userId, prompt, "Master password confirmation does not match.\n", ); @@ -601,7 +605,7 @@ export class LoginCommand { message: "Master Password Hint (optional):", }); const masterPasswordHint = hint.input; - const kdfConfig = await this.kdfConfigService.getKdfConfig(); + const kdfConfig = await this.kdfConfigService.getKdfConfig(userId); // Create new key and hash new password const newMasterKey = await this.keyService.makeMasterKey( diff --git a/apps/cli/stores/chocolatey/bitwarden-cli.nuspec b/apps/cli/stores/chocolatey/bitwarden-cli.nuspec index e5ce03fa49d..f7f86bc843f 100644 --- a/apps/cli/stores/chocolatey/bitwarden-cli.nuspec +++ b/apps/cli/stores/chocolatey/bitwarden-cli.nuspec @@ -10,7 +10,7 @@ Bitwarden Inc. https://bitwarden.com/ https://raw.githubusercontent.com/bitwarden/brand/master/icons/256x256.png - Copyright © 2015-2024 Bitwarden Inc. + Copyright © 2015-2025 Bitwarden Inc. https://github.com/bitwarden/clients/ https://help.bitwarden.com/article/cli/ https://github.com/bitwarden/clients/issues diff --git a/apps/desktop/README.md b/apps/desktop/README.md index 6578699369b..ee13f451641 100644 --- a/apps/desktop/README.md +++ b/apps/desktop/README.md @@ -1,4 +1,4 @@ -[](https://github.com/bitwarden/clients/actions/workflows/build-desktop.yml?query=branch:master) +[](https://github.com/bitwarden/clients/actions/workflows/build-desktop.yml?query=branch:main) [](https://crowdin.com/project/bitwarden-desktop) [](https://gitter.im/bitwarden/Lobby) diff --git a/apps/desktop/desktop_native/Cargo.lock b/apps/desktop/desktop_native/Cargo.lock index 04bc4887ff7..c225dc49f73 100644 --- a/apps/desktop/desktop_native/Cargo.lock +++ b/apps/desktop/desktop_native/Cargo.lock @@ -410,26 +410,6 @@ dependencies = [ "serde", ] -[[package]] -name = "bindgen" -version = "0.71.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f58bf3d7db68cfbac37cfc485a8d711e87e064c3d0fe0435b92f7a407f9d6b3" -dependencies = [ - "bitflags", - "cexpr", - "clang-sys", - "itertools", - "log", - "prettyplease", - "proc-macro2", - "quote", - "regex", - "rustc-hash", - "shlex", - "syn", -] - [[package]] name = "bitflags" version = "2.8.0" @@ -573,15 +553,6 @@ dependencies = [ "shlex", ] -[[package]] -name = "cexpr" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6fac387a98bb7c37292057cffc56d62ecb629900026402633ae9160df93a8766" -dependencies = [ - "nom", -] - [[package]] name = "cfg-if" version = "1.0.0" @@ -622,17 +593,6 @@ dependencies = [ "zeroize", ] -[[package]] -name = "clang-sys" -version = "1.8.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b023947811758c97c59bf9d1c188fd619ad4718dcaa767947df1cadb14f39f4" -dependencies = [ - "glob", - "libc", - "libloading", -] - [[package]] name = "clap" version = "4.5.31" @@ -987,6 +947,7 @@ dependencies = [ "base64", "desktop_core", "hex", + "log", "napi", "napi-build", "napi-derive", @@ -1492,15 +1453,6 @@ version = "1.70.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf" -[[package]] -name = "itertools" -version = "0.13.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "413ee7dfc52ee1a4949ceeb7dbc8a33f2d6c088194d9f922fb8318faf1f01186" -dependencies = [ - "either", -] - [[package]] name = "itoa" version = "1.0.14" @@ -1539,9 +1491,9 @@ dependencies = [ [[package]] name = "libc" -version = "0.2.169" +version = "0.2.172" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b5aba8db14291edd000dfcc4d620c7ebfb122c613afb886ca8803fa4e128a20a" +checksum = "d750af042f7ef4f724306de029d18836c26c1765a54a6a3f094cbd23a7267ffa" [[package]] name = "libloading" @@ -2158,18 +2110,18 @@ dependencies = [ [[package]] name = "pin-project" -version = "1.1.8" +version = "1.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e2ec53ad785f4d35dac0adea7f7dc6f1bb277ad84a680c7afefeae05d1f5916" +checksum = "677f1add503faace112b9f1373e43e9e054bfdd22ff1a63c1bc485eaec6a6a8a" dependencies = [ "pin-project-internal", ] [[package]] name = "pin-project-internal" -version = "1.1.8" +version = "1.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d56a66c0c55993aa927429d0f8a0abfd74f084e4d9c192cffed01e418d83eefb" +checksum = "6e918e4ff8c4549eb882f14b3a4bc8c8bc93de829416eacf579f1207a8fbf861" dependencies = [ "proc-macro2", "quote", @@ -2302,16 +2254,6 @@ dependencies = [ "zerocopy", ] -[[package]] -name = "prettyplease" -version = "0.2.29" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6924ced06e1f7dfe3fa48d57b9f74f55d8915f5036121bef647ef4b204895fac" -dependencies = [ - "proc-macro2", - "syn", -] - [[package]] name = "proc-macro-crate" version = "3.2.0" @@ -2455,9 +2397,9 @@ checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" [[package]] name = "rsa" -version = "0.9.6" +version = "0.9.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5d0e5124fcb30e76a7e79bfee683a2746db83784b86289f6251b54b7950a0dfc" +checksum = "78928ac1ed176a5ca1d17e578a1825f3d81ca54cf41053a592584b020cfd691b" dependencies = [ "const-oid", "digest", @@ -2490,12 +2432,6 @@ version = "0.1.24" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f" -[[package]] -name = "rustc-hash" -version = "2.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "357703d41365b4b27c590e3ed91eabb1b663f07c4c084095e60cbed4362dff0d" - [[package]] name = "rustc_version" version = "0.4.1" @@ -3086,9 +3022,9 @@ dependencies = [ [[package]] name = "typenum" -version = "1.17.0" +version = "1.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" +checksum = "1dccffe3ce07af9386bfd29e80c0ab1a8205a2fc34e4bcd40364df902cfa8f3f" [[package]] name = "uds_windows" @@ -3735,7 +3671,6 @@ checksum = "581fee95406bb13382d2f65cd4a908ca7b1e4c2f1917f143ba16efe98a589b5d" name = "windows_plugin_authenticator" version = "0.0.0" dependencies = [ - "bindgen", "hex", "windows 0.61.1", "windows-core 0.61.0", diff --git a/apps/desktop/desktop_native/Cargo.toml b/apps/desktop/desktop_native/Cargo.toml index 01838359147..d9e61124864 100644 --- a/apps/desktop/desktop_native/Cargo.toml +++ b/apps/desktop/desktop_native/Cargo.toml @@ -28,17 +28,17 @@ hex = "=0.4.3" homedir = "=0.3.4" interprocess = "=2.2.1" keytar = "=0.1.6" -libc = "=0.2.169" +libc = "=0.2.172" log = "=0.4.25" napi = "=2.16.15" napi-build = "=2.1.4" napi-derive = "=2.16.13" oo7 = "=0.3.3" oslog = "=0.2.0" -pin-project = "=1.1.8" +pin-project = "=1.1.10" pkcs8 = "=0.10.2" rand = "=0.8.5" -rsa = "=0.9.6" +rsa = "=0.9.8" russh-cryptovec = "=0.7.3" scopeguard = "=1.2.0" security-framework = "=3.1.0" @@ -54,7 +54,7 @@ thiserror = "=1.0.69" tokio = "=1.43.1" tokio-stream = "=0.1.15" tokio-util = "=0.7.13" -typenum = "=1.17.0" +typenum = "=1.18.0" uniffi = "=0.28.3" widestring = "=1.1.0" windows = "=0.61.1" diff --git a/apps/desktop/desktop_native/build.js b/apps/desktop/desktop_native/build.js index ec20dce4116..da61da15f9d 100644 --- a/apps/desktop/desktop_native/build.js +++ b/apps/desktop/desktop_native/build.js @@ -3,6 +3,21 @@ const child_process = require("child_process"); const fs = require("fs"); const path = require("path"); const process = require("process"); + +// Map of the Node arch equivalents for the rust target triplets, used to move the file to the correct location +const rustTargetsMap = { + "i686-pc-windows-msvc": { nodeArch: 'ia32', platform: 'win32' }, + "x86_64-pc-windows-msvc": { nodeArch: 'x64', platform: 'win32' }, + "aarch64-pc-windows-msvc": { nodeArch: 'arm64', platform: 'win32' }, + "x86_64-apple-darwin": { nodeArch: 'x64', platform: 'darwin' }, + "aarch64-apple-darwin": { nodeArch: 'arm64', platform: 'darwin' }, + 'x86_64-unknown-linux-musl': { nodeArch: 'x64', platform: 'linux' }, + 'aarch64-unknown-linux-musl': { nodeArch: 'arm64', platform: 'linux' }, +} + +// Ensure the dist directory exists +fs.mkdirSync(path.join(__dirname, "dist"), { recursive: true }); + const args = process.argv.slice(2); // Get arguments passed to the script const mode = args.includes("--release") ? "release" : "debug"; const targetArg = args.find(arg => arg.startsWith("--target=")); @@ -13,13 +28,21 @@ let crossPlatform = process.argv.length > 2 && process.argv[2] === "cross-platfo function buildNapiModule(target, release = true) { const targetArg = target ? `--target ${target}` : ""; const releaseArg = release ? "--release" : ""; - return child_process.execSync(`npm run build -- ${releaseArg} ${targetArg}`, { stdio: 'inherit', cwd: path.join(__dirname, "napi") }); + child_process.execSync(`npm run build -- ${releaseArg} ${targetArg}`, { stdio: 'inherit', cwd: path.join(__dirname, "napi") }); } function buildProxyBin(target, release = true) { const targetArg = target ? `--target ${target}` : ""; const releaseArg = release ? "--release" : ""; - return child_process.execSync(`cargo build --bin desktop_proxy ${releaseArg} ${targetArg}`, {stdio: 'inherit', cwd: path.join(__dirname, "proxy")}); + child_process.execSync(`cargo build --bin desktop_proxy ${releaseArg} ${targetArg}`, {stdio: 'inherit', cwd: path.join(__dirname, "proxy")}); + + if (target) { + // Copy the resulting binary to the dist folder + const targetFolder = release ? "release" : "debug"; + const ext = process.platform === "win32" ? ".exe" : ""; + const nodeArch = rustTargetsMap[target].nodeArch; + fs.copyFileSync(path.join(__dirname, "target", target, targetFolder, `desktop_proxy${ext}`), path.join(__dirname, "dist", `desktop_proxy.${process.platform}-${nodeArch}${ext}`)); + } } if (!crossPlatform && !target) { @@ -36,45 +59,17 @@ if (target) { return; } -// Note that targets contains pairs of [rust target, node arch] -// We do this to move the output binaries to a location that can -// be easily accessed from electron-builder using ${os} and ${arch} -let targets = []; -switch (process.platform) { - case "win32": - targets = [ - ["i686-pc-windows-msvc", 'ia32'], - ["x86_64-pc-windows-msvc", 'x64'], - ["aarch64-pc-windows-msvc", 'arm64'] - ]; - break; +// Filter the targets based on the current platform, and build for each of them +let platformTargets = Object.entries(rustTargetsMap).filter(([_, { platform: p }]) => p === process.platform); +console.log("Cross building native modules for the targets: ", platformTargets.map(([target, _]) => target).join(", ")); - case "darwin": - targets = [ - ["x86_64-apple-darwin", 'x64'], - ["aarch64-apple-darwin", 'arm64'] - ]; - break; - - default: - targets = [ - ['x86_64-unknown-linux-musl', 'x64'], - ['aarch64-unknown-linux-musl', 'arm64'] - ]; - - process.env["PKG_CONFIG_ALLOW_CROSS"] = "1"; - process.env["PKG_CONFIG_ALL_STATIC"] = "1"; - break; +// When building for Linux, we need to set some environment variables to allow cross-compilation +if (process.platform === "linux") { + process.env["PKG_CONFIG_ALLOW_CROSS"] = "1"; + process.env["PKG_CONFIG_ALL_STATIC"] = "1"; } -console.log("Cross building native modules for the targets: ", targets.map(([target, _]) => target).join(", ")); - -fs.mkdirSync(path.join(__dirname, "dist"), { recursive: true }); - -targets.forEach(([target, nodeArch]) => { +platformTargets.forEach(([target, _]) => { buildNapiModule(target); buildProxyBin(target); - - const ext = process.platform === "win32" ? ".exe" : ""; - fs.copyFileSync(path.join(__dirname, "target", target, "release", `desktop_proxy${ext}`), path.join(__dirname, "dist", `desktop_proxy.${process.platform}-${nodeArch}${ext}`)); }); diff --git a/apps/desktop/desktop_native/napi/Cargo.toml b/apps/desktop/desktop_native/napi/Cargo.toml index d59581a5e2e..669f166e748 100644 --- a/apps/desktop/desktop_native/napi/Cargo.toml +++ b/apps/desktop/desktop_native/napi/Cargo.toml @@ -18,6 +18,7 @@ base64 = { workspace = true } hex = { workspace = true } anyhow = { workspace = true } desktop_core = { path = "../core" } +log = { workspace = true } napi = { workspace = true, features = ["async"] } napi-derive = { workspace = true } serde = { workspace = true, features = ["derive"] } diff --git a/apps/desktop/desktop_native/napi/index.d.ts b/apps/desktop/desktop_native/napi/index.d.ts index 6ded4e3db93..952f2571c5d 100644 --- a/apps/desktop/desktop_native/napi/index.d.ts +++ b/apps/desktop/desktop_native/napi/index.d.ts @@ -185,3 +185,13 @@ export declare namespace crypto { export declare namespace passkey_authenticator { export function register(): void } +export declare namespace logging { + export const enum LogLevel { + Trace = 0, + Debug = 1, + Info = 2, + Warn = 3, + Error = 4 + } + export function initNapiLog(jsLogFn: (err: Error | null, arg0: LogLevel, arg1: string) => any): void +} diff --git a/apps/desktop/desktop_native/napi/src/lib.rs b/apps/desktop/desktop_native/napi/src/lib.rs index 8cbc526487e..37796ef6f59 100644 --- a/apps/desktop/desktop_native/napi/src/lib.rs +++ b/apps/desktop/desktop_native/napi/src/lib.rs @@ -807,3 +807,61 @@ pub mod passkey_authenticator { }) } } + +#[napi] +pub mod logging { + use log::{Level, Metadata, Record}; + use napi::threadsafe_function::{ + ErrorStrategy::CalleeHandled, ThreadsafeFunction, ThreadsafeFunctionCallMode, + }; + use std::sync::OnceLock; + struct JsLogger(OnceLock>); + static JS_LOGGER: JsLogger = JsLogger(OnceLock::new()); + + #[napi] + pub enum LogLevel { + Trace, + Debug, + Info, + Warn, + Error, + } + + impl From 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, + } + } + } + + #[napi] + pub fn init_napi_log(js_log_fn: ThreadsafeFunction<(LogLevel, String), CalleeHandled>) { + let _ = JS_LOGGER.0.set(js_log_fn); + let _ = log::set_logger(&JS_LOGGER); + log::set_max_level(log::LevelFilter::Debug); + } + + impl log::Log for JsLogger { + fn enabled(&self, metadata: &Metadata) -> bool { + metadata.level() <= log::max_level() + } + + fn log(&self, record: &Record) { + if !self.enabled(record.metadata()) { + return; + } + let Some(logger) = self.0.get() else { + return; + }; + let msg = (record.level().into(), record.args().to_string()); + let _ = logger.call(Ok(msg), ThreadsafeFunctionCallMode::NonBlocking); + } + + fn flush(&self) {} + } +} diff --git a/apps/desktop/desktop_native/windows_plugin_authenticator/Cargo.toml b/apps/desktop/desktop_native/windows_plugin_authenticator/Cargo.toml index 72a8505389e..18abb86d057 100644 --- a/apps/desktop/desktop_native/windows_plugin_authenticator/Cargo.toml +++ b/apps/desktop/desktop_native/windows_plugin_authenticator/Cargo.toml @@ -5,9 +5,6 @@ edition = { workspace = true } license = { workspace = true } publish = { workspace = true } -[target.'cfg(target_os = "windows")'.build-dependencies] -bindgen = { workspace = true } - [target.'cfg(windows)'.dependencies] windows = { workspace = true, features = ["Win32_Foundation", "Win32_Security", "Win32_System_Com", "Win32_System_LibraryLoader" ] } windows-core = { workspace = true } diff --git a/apps/desktop/desktop_native/windows_plugin_authenticator/README.md b/apps/desktop/desktop_native/windows_plugin_authenticator/README.md index 6dc72ceed46..4802c5d4243 100644 --- a/apps/desktop/desktop_native/windows_plugin_authenticator/README.md +++ b/apps/desktop/desktop_native/windows_plugin_authenticator/README.md @@ -2,22 +2,6 @@ This is an internal crate that's meant to be a safe abstraction layer over the generated Rust bindings for the Windows WebAuthn Plugin Authenticator API's. +This crate is very much a WIP and is not ready for internal use. + You can find more information about the Windows WebAuthn API's [here](https://github.com/microsoft/webauthn). - -## Building - -To build this crate, set the following environment variables: - -- `LIBCLANG_PATH` -> the path to the `bin` directory of your LLVM install ([more info](https://rust-lang.github.io/rust-bindgen/requirements.html?highlight=libclang_path#installing-clang)) - -### Bash Example - -``` -export LIBCLANG_PATH='C:\Program Files\Microsoft Visual Studio\2022\Community\VC\Tools\Llvm\x64\bin' -``` - -### PowerShell Example - -``` -$env:LIBCLANG_PATH = 'C:\Program Files\Microsoft Visual Studio\2022\Community\VC\Tools\Llvm\x64\bin' -``` diff --git a/apps/desktop/desktop_native/windows_plugin_authenticator/build.rs b/apps/desktop/desktop_native/windows_plugin_authenticator/build.rs deleted file mode 100644 index 4843145bac0..00000000000 --- a/apps/desktop/desktop_native/windows_plugin_authenticator/build.rs +++ /dev/null @@ -1,27 +0,0 @@ -fn main() { - #[cfg(target_os = "windows")] - windows(); -} - -#[cfg(target_os = "windows")] -fn windows() { - let out_dir = std::env::var("OUT_DIR").expect("OUT_DIR not set"); - - let bindings = bindgen::Builder::default() - .header("pluginauthenticator.hpp") - .parse_callbacks(Box::new(bindgen::CargoCallbacks::new())) - .allowlist_type("DWORD") - .allowlist_type("PBYTE") - .allowlist_type("EXPERIMENTAL.*") - .allowlist_function(".*EXPERIMENTAL.*") - .allowlist_function("WebAuthNGetApiVersionNumber") - .generate() - .expect("Unable to generate bindings."); - - bindings - .write_to_file(format!( - "{}\\windows_plugin_authenticator_bindings.rs", - out_dir - )) - .expect("Couldn't write bindings."); -} diff --git a/apps/desktop/desktop_native/windows_plugin_authenticator/pluginauthenticator.hpp b/apps/desktop/desktop_native/windows_plugin_authenticator/pluginauthenticator.hpp deleted file mode 100644 index c800266a3e6..00000000000 --- a/apps/desktop/desktop_native/windows_plugin_authenticator/pluginauthenticator.hpp +++ /dev/null @@ -1,231 +0,0 @@ -/* - Bitwarden's pluginauthenticator.hpp - - Source: https://github.com/microsoft/webauthn/blob/master/experimental/pluginauthenticator.h - - This is a C++ header file, so the extension has been manually - changed from `.h` to `.hpp`, so bindgen will automatically - generate the correct C++ bindings. - - More Info: https://rust-lang.github.io/rust-bindgen/cpp.html -*/ - -/* this ALWAYS GENERATED file contains the definitions for the interfaces */ - -/* File created by MIDL compiler version 8.01.0628 */ -/* @@MIDL_FILE_HEADING( ) */ - -/* verify that the version is high enough to compile this file*/ -#ifndef __REQUIRED_RPCNDR_H_VERSION__ -#define __REQUIRED_RPCNDR_H_VERSION__ 501 -#endif - -/* verify that the version is high enough to compile this file*/ -#ifndef __REQUIRED_RPCSAL_H_VERSION__ -#define __REQUIRED_RPCSAL_H_VERSION__ 100 -#endif - -#include "rpc.h" -#include "rpcndr.h" - -#ifndef __RPCNDR_H_VERSION__ -#error this stub requires an updated version of -#endif /* __RPCNDR_H_VERSION__ */ - -#ifndef COM_NO_WINDOWS_H -#include "windows.h" -#include "ole2.h" -#endif /*COM_NO_WINDOWS_H*/ - -#ifndef __pluginauthenticator_h__ -#define __pluginauthenticator_h__ - -#if defined(_MSC_VER) && (_MSC_VER >= 1020) -#pragma once -#endif - -#ifndef DECLSPEC_XFGVIRT -#if defined(_CONTROL_FLOW_GUARD_XFG) -#define DECLSPEC_XFGVIRT(base, func) __declspec(xfg_virtual(base, func)) -#else -#define DECLSPEC_XFGVIRT(base, func) -#endif -#endif - -/* Forward Declarations */ - -#ifndef __EXPERIMENTAL_IPluginAuthenticator_FWD_DEFINED__ -#define __EXPERIMENTAL_IPluginAuthenticator_FWD_DEFINED__ -typedef interface EXPERIMENTAL_IPluginAuthenticator EXPERIMENTAL_IPluginAuthenticator; - -#endif /* __EXPERIMENTAL_IPluginAuthenticator_FWD_DEFINED__ */ - -/* header files for imported files */ -#include "oaidl.h" -#include "webauthn.h" - -#ifdef __cplusplus -extern "C"{ -#endif - -/* interface __MIDL_itf_pluginauthenticator_0000_0000 */ -/* [local] */ - -typedef struct _EXPERIMENTAL_WEBAUTHN_PLUGIN_OPERATION_REQUEST - { - HWND hWnd; - GUID transactionId; - DWORD cbRequestSignature; - /* [size_is] */ byte *pbRequestSignature; - DWORD cbEncodedRequest; - /* [size_is] */ byte *pbEncodedRequest; - } EXPERIMENTAL_WEBAUTHN_PLUGIN_OPERATION_REQUEST; - -typedef struct _EXPERIMENTAL_WEBAUTHN_PLUGIN_OPERATION_REQUEST *EXPERIMENTAL_PWEBAUTHN_PLUGIN_OPERATION_REQUEST; - -typedef const EXPERIMENTAL_WEBAUTHN_PLUGIN_OPERATION_REQUEST *EXPERIMENTAL_PCWEBAUTHN_PLUGIN_OPERATION_REQUEST; - -typedef struct _EXPERIMENTAL_WEBAUTHN_PLUGIN_OPERATION_RESPONSE - { - DWORD cbEncodedResponse; - /* [size_is] */ byte *pbEncodedResponse; - } EXPERIMENTAL_WEBAUTHN_PLUGIN_OPERATION_RESPONSE; - -typedef struct _EXPERIMENTAL_WEBAUTHN_PLUGIN_OPERATION_RESPONSE *EXPERIMENTAL_PWEBAUTHN_PLUGIN_OPERATION_RESPONSE; - -typedef const EXPERIMENTAL_WEBAUTHN_PLUGIN_OPERATION_RESPONSE *EXPERIMENTAL_PCWEBAUTHN_PLUGIN_OPERATION_RESPONSE; - -typedef struct _EXPERIMENTAL_WEBAUTHN_PLUGIN_CANCEL_OPERATION_REQUEST - { - GUID transactionId; - DWORD cbRequestSignature; - /* [size_is] */ byte *pbRequestSignature; - } EXPERIMENTAL_WEBAUTHN_PLUGIN_CANCEL_OPERATION_REQUEST; - -typedef struct _EXPERIMENTAL_WEBAUTHN_PLUGIN_CANCEL_OPERATION_REQUEST *EXPERIMENTAL_PWEBAUTHN_PLUGIN_CANCEL_OPERATION_REQUEST; - -typedef const EXPERIMENTAL_WEBAUTHN_PLUGIN_CANCEL_OPERATION_REQUEST *EXPERIMENTAL_PCWEBAUTHN_PLUGIN_CANCEL_OPERATION_REQUEST; - -extern RPC_IF_HANDLE __MIDL_itf_pluginauthenticator_0000_0000_v0_0_c_ifspec; -extern RPC_IF_HANDLE __MIDL_itf_pluginauthenticator_0000_0000_v0_0_s_ifspec; - -#ifndef __EXPERIMENTAL_IPluginAuthenticator_INTERFACE_DEFINED__ -#define __EXPERIMENTAL_IPluginAuthenticator_INTERFACE_DEFINED__ - -/* interface EXPERIMENTAL_IPluginAuthenticator */ -/* [unique][version][uuid][object] */ - -EXTERN_C const IID IID_EXPERIMENTAL_IPluginAuthenticator; - -#if defined(__cplusplus) && !defined(CINTERFACE) - - MIDL_INTERFACE("e6466e9a-b2f3-47c5-b88d-89bc14a8d998") - EXPERIMENTAL_IPluginAuthenticator : public IUnknown - { - public: - virtual HRESULT STDMETHODCALLTYPE EXPERIMENTAL_PluginMakeCredential( - /* [in] */ __RPC__in EXPERIMENTAL_PCWEBAUTHN_PLUGIN_OPERATION_REQUEST request, - /* [out] */ __RPC__deref_out_opt EXPERIMENTAL_PWEBAUTHN_PLUGIN_OPERATION_RESPONSE *response) = 0; - - virtual HRESULT STDMETHODCALLTYPE EXPERIMENTAL_PluginGetAssertion( - /* [in] */ __RPC__in EXPERIMENTAL_PCWEBAUTHN_PLUGIN_OPERATION_REQUEST request, - /* [out] */ __RPC__deref_out_opt EXPERIMENTAL_PWEBAUTHN_PLUGIN_OPERATION_RESPONSE *response) = 0; - - virtual HRESULT STDMETHODCALLTYPE EXPERIMENTAL_PluginCancelOperation( - /* [in] */ __RPC__in EXPERIMENTAL_PCWEBAUTHN_PLUGIN_CANCEL_OPERATION_REQUEST request) = 0; - - }; - -#else /* C style interface */ - - typedef struct EXPERIMENTAL_IPluginAuthenticatorVtbl - { - BEGIN_INTERFACE - - DECLSPEC_XFGVIRT(IUnknown, QueryInterface) - HRESULT ( STDMETHODCALLTYPE *QueryInterface )( - __RPC__in EXPERIMENTAL_IPluginAuthenticator * This, - /* [in] */ __RPC__in REFIID riid, - /* [annotation][iid_is][out] */ - _COM_Outptr_ void **ppvObject); - - DECLSPEC_XFGVIRT(IUnknown, AddRef) - ULONG ( STDMETHODCALLTYPE *AddRef )( - __RPC__in EXPERIMENTAL_IPluginAuthenticator * This); - - DECLSPEC_XFGVIRT(IUnknown, Release) - ULONG ( STDMETHODCALLTYPE *Release )( - __RPC__in EXPERIMENTAL_IPluginAuthenticator * This); - - DECLSPEC_XFGVIRT(EXPERIMENTAL_IPluginAuthenticator, EXPERIMENTAL_PluginMakeCredential) - HRESULT ( STDMETHODCALLTYPE *EXPERIMENTAL_PluginMakeCredential )( - __RPC__in EXPERIMENTAL_IPluginAuthenticator * This, - /* [in] */ __RPC__in EXPERIMENTAL_PCWEBAUTHN_PLUGIN_OPERATION_REQUEST request, - /* [out] */ __RPC__deref_out_opt EXPERIMENTAL_PWEBAUTHN_PLUGIN_OPERATION_RESPONSE *response); - - DECLSPEC_XFGVIRT(EXPERIMENTAL_IPluginAuthenticator, EXPERIMENTAL_PluginGetAssertion) - HRESULT ( STDMETHODCALLTYPE *EXPERIMENTAL_PluginGetAssertion )( - __RPC__in EXPERIMENTAL_IPluginAuthenticator * This, - /* [in] */ __RPC__in EXPERIMENTAL_PCWEBAUTHN_PLUGIN_OPERATION_REQUEST request, - /* [out] */ __RPC__deref_out_opt EXPERIMENTAL_PWEBAUTHN_PLUGIN_OPERATION_RESPONSE *response); - - DECLSPEC_XFGVIRT(EXPERIMENTAL_IPluginAuthenticator, EXPERIMENTAL_PluginCancelOperation) - HRESULT ( STDMETHODCALLTYPE *EXPERIMENTAL_PluginCancelOperation )( - __RPC__in EXPERIMENTAL_IPluginAuthenticator * This, - /* [in] */ __RPC__in EXPERIMENTAL_PCWEBAUTHN_PLUGIN_CANCEL_OPERATION_REQUEST request); - - END_INTERFACE - } EXPERIMENTAL_IPluginAuthenticatorVtbl; - - interface EXPERIMENTAL_IPluginAuthenticator - { - CONST_VTBL struct EXPERIMENTAL_IPluginAuthenticatorVtbl *lpVtbl; - }; - -#ifdef COBJMACROS - - -#define EXPERIMENTAL_IPluginAuthenticator_QueryInterface(This,riid,ppvObject) \ - ( (This)->lpVtbl -> QueryInterface(This,riid,ppvObject) ) - -#define EXPERIMENTAL_IPluginAuthenticator_AddRef(This) \ - ( (This)->lpVtbl -> AddRef(This) ) - -#define EXPERIMENTAL_IPluginAuthenticator_Release(This) \ - ( (This)->lpVtbl -> Release(This) ) - - -#define EXPERIMENTAL_IPluginAuthenticator_EXPERIMENTAL_PluginMakeCredential(This,request,response) \ - ( (This)->lpVtbl -> EXPERIMENTAL_PluginMakeCredential(This,request,response) ) - -#define EXPERIMENTAL_IPluginAuthenticator_EXPERIMENTAL_PluginGetAssertion(This,request,response) \ - ( (This)->lpVtbl -> EXPERIMENTAL_PluginGetAssertion(This,request,response) ) - -#define EXPERIMENTAL_IPluginAuthenticator_EXPERIMENTAL_PluginCancelOperation(This,request) \ - ( (This)->lpVtbl -> EXPERIMENTAL_PluginCancelOperation(This,request) ) - -#endif /* COBJMACROS */ - -#endif /* C style interface */ - -#endif /* __EXPERIMENTAL_IPluginAuthenticator_INTERFACE_DEFINED__ */ - -/* Additional Prototypes for ALL interfaces */ - -unsigned long __RPC_USER HWND_UserSize( __RPC__in unsigned long *, unsigned long , __RPC__in HWND * ); -unsigned char * __RPC_USER HWND_UserMarshal( __RPC__in unsigned long *, __RPC__inout_xcount(0) unsigned char *, __RPC__in HWND * ); -unsigned char * __RPC_USER HWND_UserUnmarshal(__RPC__in unsigned long *, __RPC__in_xcount(0) unsigned char *, __RPC__out HWND * ); -void __RPC_USER HWND_UserFree( __RPC__in unsigned long *, __RPC__in HWND * ); - -unsigned long __RPC_USER HWND_UserSize64( __RPC__in unsigned long *, unsigned long , __RPC__in HWND * ); -unsigned char * __RPC_USER HWND_UserMarshal64( __RPC__in unsigned long *, __RPC__inout_xcount(0) unsigned char *, __RPC__in HWND * ); -unsigned char * __RPC_USER HWND_UserUnmarshal64(__RPC__in unsigned long *, __RPC__in_xcount(0) unsigned char *, __RPC__out HWND * ); -void __RPC_USER HWND_UserFree64( __RPC__in unsigned long *, __RPC__in HWND * ); - -/* end of Additional Prototypes */ - -#ifdef __cplusplus -} -#endif - -#endif diff --git a/apps/desktop/desktop_native/windows_plugin_authenticator/src/lib.rs b/apps/desktop/desktop_native/windows_plugin_authenticator/src/lib.rs index fe2e35df2f8..cdea50aee99 100644 --- a/apps/desktop/desktop_native/windows_plugin_authenticator/src/lib.rs +++ b/apps/desktop/desktop_native/windows_plugin_authenticator/src/lib.rs @@ -2,15 +2,6 @@ #![allow(non_snake_case)] #![allow(non_camel_case_types)] -mod pa; - -use pa::{ - DWORD, EXPERIMENTAL_PCWEBAUTHN_PLUGIN_CANCEL_OPERATION_REQUEST, - EXPERIMENTAL_PCWEBAUTHN_PLUGIN_OPERATION_REQUEST, - EXPERIMENTAL_PWEBAUTHN_PLUGIN_ADD_AUTHENTICATOR_RESPONSE, - EXPERIMENTAL_PWEBAUTHN_PLUGIN_OPERATION_RESPONSE, - EXPERIMENTAL_WEBAUTHN_PLUGIN_ADD_AUTHENTICATOR_RESPONSE, PBYTE, -}; use std::ffi::c_uchar; use std::ptr; use windows::Win32::Foundation::*; @@ -18,16 +9,14 @@ use windows::Win32::System::Com::*; use windows::Win32::System::LibraryLoader::*; use windows_core::*; +mod pluginauthenticator; +mod webauthn; + const AUTHENTICATOR_NAME: &str = "Bitwarden Desktop Authenticator"; //const AAGUID: &str = "d548826e-79b4-db40-a3d8-11116f7e8349"; const CLSID: &str = "0f7dc5d9-69ce-4652-8572-6877fd695062"; const RPID: &str = "bitwarden.com"; -/// Returns the current Windows WebAuthN version. -pub fn get_version_number() -> u32 { - unsafe { pa::WebAuthNGetApiVersionNumber() } -} - /// Handles initialization and registration for the Bitwarden desktop app as a /// plugin authenticator with Windows. /// For now, also adds the authenticator @@ -76,7 +65,8 @@ fn initialize_com_library() -> std::result::Result<(), String> { /// Registers the Bitwarden Plugin Authenticator COM library with Windows. fn register_com_library() -> std::result::Result<(), String> { - static FACTORY: windows_core::StaticComObject = Factory().into_static(); + static FACTORY: windows_core::StaticComObject = + pluginauthenticator::Factory().into_static(); let clsid: *const GUID = &GUID::from_u128(0xa98925d161f640de9327dc418fcb2ff4); match unsafe { @@ -113,25 +103,25 @@ fn add_authenticator() -> std::result::Result<(), String> { let cbor_authenticator_info = "A60182684649444F5F325F30684649444F5F325F310282637072666B686D61632D7365637265740350D548826E79B4DB40A3D811116F7E834904A362726BF5627570F5627576F5098168696E7465726E616C0A81A263616C672664747970656A7075626C69632D6B6579"; let mut authenticator_info_bytes = hex::decode(cbor_authenticator_info).unwrap(); - let add_authenticator_options = EXPERIMENTAL_WEBAUTHN_PLUGIN_ADD_AUTHENTICATOR_OPTIONS { - pwszAuthenticatorName: authenticator_name_ptr, - pwszPluginClsId: clsid_ptr, - pwszPluginRpId: relying_party_id_ptr, - pwszLightThemeLogo: ptr::null(), // unused by Windows - pwszDarkThemeLogo: ptr::null(), // unused by Windows - cbAuthenticatorInfo: authenticator_info_bytes.len() as u32, - pbAuthenticatorInfo: authenticator_info_bytes.as_mut_ptr(), + let add_authenticator_options = webauthn::ExperimentalWebAuthnPluginAddAuthenticatorOptions { + authenticator_name: authenticator_name_ptr, + com_clsid: clsid_ptr, + rpid: relying_party_id_ptr, + light_theme_logo: ptr::null(), // unused by Windows + dark_theme_logo: ptr::null(), // unused by Windows + cbor_authenticator_info_byte_count: authenticator_info_bytes.len() as u32, + cbor_authenticator_info: authenticator_info_bytes.as_mut_ptr(), }; - let plugin_signing_public_key_byte_count: DWORD = 0; + let plugin_signing_public_key_byte_count: u32 = 0; let mut plugin_signing_public_key: c_uchar = 0; - let plugin_signing_public_key_ptr: PBYTE = &mut plugin_signing_public_key; + let plugin_signing_public_key_ptr = &mut plugin_signing_public_key; - let mut add_response = EXPERIMENTAL_WEBAUTHN_PLUGIN_ADD_AUTHENTICATOR_RESPONSE { - cbOpSignPubKey: plugin_signing_public_key_byte_count, - pbOpSignPubKey: plugin_signing_public_key_ptr, + let mut add_response = webauthn::ExperimentalWebAuthnPluginAddAuthenticatorResponse { + plugin_operation_signing_key_byte_count: plugin_signing_public_key_byte_count, + plugin_operation_signing_key: plugin_signing_public_key_ptr, }; - let mut add_response_ptr: *mut EXPERIMENTAL_WEBAUTHN_PLUGIN_ADD_AUTHENTICATOR_RESPONSE = + let mut add_response_ptr: *mut webauthn::ExperimentalWebAuthnPluginAddAuthenticatorResponse = &mut add_response; let result = unsafe { @@ -160,23 +150,10 @@ fn add_authenticator() -> std::result::Result<(), String> { } } -#[repr(C)] -#[derive(Debug, Copy, Clone)] -pub struct EXPERIMENTAL_WEBAUTHN_PLUGIN_ADD_AUTHENTICATOR_OPTIONS { - pub pwszAuthenticatorName: *const u16, - pub pwszPluginClsId: *const u16, - pub pwszPluginRpId: *const u16, - pub pwszLightThemeLogo: *const u16, - pub pwszDarkThemeLogo: *const u16, - pub cbAuthenticatorInfo: u32, - pub pbAuthenticatorInfo: *const u8, -} - type EXPERIMENTAL_WebAuthNPluginAddAuthenticatorFnDeclaration = unsafe extern "cdecl" fn( - pPluginAddAuthenticatorOptions: *const EXPERIMENTAL_WEBAUTHN_PLUGIN_ADD_AUTHENTICATOR_OPTIONS, - ppPluginAddAuthenticatorResponse: *mut EXPERIMENTAL_PWEBAUTHN_PLUGIN_ADD_AUTHENTICATOR_RESPONSE, -) - -> HRESULT; + pPluginAddAuthenticatorOptions: *const webauthn::ExperimentalWebAuthnPluginAddAuthenticatorOptions, + ppPluginAddAuthenticatorResponse: *mut *mut webauthn::ExperimentalWebAuthnPluginAddAuthenticatorResponse, +) -> HRESULT; unsafe fn delay_load(library: PCSTR, function: PCSTR) -> Option { let library = LoadLibraryExA(library, None, LOAD_LIBRARY_SEARCH_DEFAULT_DIRS); @@ -195,70 +172,3 @@ unsafe fn delay_load(library: PCSTR, function: PCSTR) -> Option { None } - -#[interface("e6466e9a-b2f3-47c5-b88d-89bc14a8d998")] -unsafe trait EXPERIMENTAL_IPluginAuthenticator: IUnknown { - fn EXPERIMENTAL_PluginMakeCredential( - &self, - request: EXPERIMENTAL_PCWEBAUTHN_PLUGIN_OPERATION_REQUEST, - response: *mut EXPERIMENTAL_PWEBAUTHN_PLUGIN_OPERATION_RESPONSE, - ) -> HRESULT; - fn EXPERIMENTAL_PluginGetAssertion( - &self, - request: EXPERIMENTAL_PCWEBAUTHN_PLUGIN_OPERATION_REQUEST, - response: *mut EXPERIMENTAL_PWEBAUTHN_PLUGIN_OPERATION_RESPONSE, - ) -> HRESULT; - fn EXPERIMENTAL_PluginCancelOperation( - &self, - request: EXPERIMENTAL_PCWEBAUTHN_PLUGIN_CANCEL_OPERATION_REQUEST, - ) -> HRESULT; -} - -#[implement(EXPERIMENTAL_IPluginAuthenticator)] -struct PACOMObject; - -impl EXPERIMENTAL_IPluginAuthenticator_Impl for PACOMObject_Impl { - unsafe fn EXPERIMENTAL_PluginMakeCredential( - &self, - _request: EXPERIMENTAL_PCWEBAUTHN_PLUGIN_OPERATION_REQUEST, - _response: *mut EXPERIMENTAL_PWEBAUTHN_PLUGIN_OPERATION_RESPONSE, - ) -> HRESULT { - HRESULT(0) - } - - unsafe fn EXPERIMENTAL_PluginGetAssertion( - &self, - _request: EXPERIMENTAL_PCWEBAUTHN_PLUGIN_OPERATION_REQUEST, - _response: *mut EXPERIMENTAL_PWEBAUTHN_PLUGIN_OPERATION_RESPONSE, - ) -> HRESULT { - HRESULT(0) - } - - unsafe fn EXPERIMENTAL_PluginCancelOperation( - &self, - _request: EXPERIMENTAL_PCWEBAUTHN_PLUGIN_CANCEL_OPERATION_REQUEST, - ) -> HRESULT { - HRESULT(0) - } -} - -#[implement(IClassFactory)] -struct Factory(); - -impl IClassFactory_Impl for Factory_Impl { - fn CreateInstance( - &self, - outer: Ref, - iid: *const GUID, - object: *mut *mut core::ffi::c_void, - ) -> Result<()> { - assert!(outer.is_null()); - let unknown: IInspectable = PACOMObject.into(); - unsafe { unknown.query(iid, object).ok() } - } - - fn LockServer(&self, lock: BOOL) -> Result<()> { - assert!(lock.as_bool()); - Ok(()) - } -} diff --git a/apps/desktop/desktop_native/windows_plugin_authenticator/src/pa.rs b/apps/desktop/desktop_native/windows_plugin_authenticator/src/pa.rs deleted file mode 100644 index 7c93399594d..00000000000 --- a/apps/desktop/desktop_native/windows_plugin_authenticator/src/pa.rs +++ /dev/null @@ -1,15 +0,0 @@ -/* - The 'pa' (plugin authenticator) module will contain the generated - bindgen code. - - The attributes below will suppress warnings from the generated code. -*/ - -#![cfg(target_os = "windows")] -#![allow(clippy::all)] -#![allow(warnings)] - -include!(concat!( - env!("OUT_DIR"), - "/windows_plugin_authenticator_bindings.rs" -)); diff --git a/apps/desktop/desktop_native/windows_plugin_authenticator/src/pluginauthenticator.rs b/apps/desktop/desktop_native/windows_plugin_authenticator/src/pluginauthenticator.rs new file mode 100644 index 00000000000..132f9effcde --- /dev/null +++ b/apps/desktop/desktop_native/windows_plugin_authenticator/src/pluginauthenticator.rs @@ -0,0 +1,110 @@ +/* + This file exposes the functions and types defined here: https://github.com/microsoft/webauthn/blob/master/experimental/pluginauthenticator.h +*/ + +use windows::Win32::System::Com::*; +use windows_core::*; + +/// Used when creating and asserting credentials. +/// Header File Name: _EXPERIMENTAL_WEBAUTHN_PLUGIN_OPERATION_REQUEST +/// Header File Usage: EXPERIMENTAL_PluginMakeCredential() +/// EXPERIMENTAL_PluginGetAssertion() +#[repr(C)] +#[derive(Debug, Copy, Clone)] +pub struct ExperimentalWebAuthnPluginOperationRequest { + pub window_handle: windows::Win32::Foundation::HWND, + pub transaction_id: windows_core::GUID, + pub request_signature_byte_count: u32, + pub request_signature_pointer: *mut u8, + pub encoded_request_byte_count: u32, + pub encoded_request_pointer: *mut u8, +} + +/// Used as a response when creating and asserting credentials. +/// Header File Name: _EXPERIMENTAL_WEBAUTHN_PLUGIN_OPERATION_RESPONSE +/// Header File Usage: EXPERIMENTAL_PluginMakeCredential() +/// EXPERIMENTAL_PluginGetAssertion() +#[repr(C)] +#[derive(Debug, Copy, Clone)] +pub struct ExperimentalWebAuthnPluginOperationResponse { + pub encoded_response_byte_count: u32, + pub encoded_response_pointer: *mut u8, +} + +/// Used to cancel an operation. +/// Header File Name: _EXPERIMENTAL_WEBAUTHN_PLUGIN_CANCEL_OPERATION_REQUEST +/// Header File Usage: EXPERIMENTAL_PluginCancelOperation() +#[repr(C)] +#[derive(Debug, Copy, Clone)] +pub struct ExperimentalWebAuthnPluginCancelOperationRequest { + pub transaction_id: windows_core::GUID, + pub request_signature_byte_count: u32, + pub request_signature_pointer: *mut u8, +} + +#[interface("e6466e9a-b2f3-47c5-b88d-89bc14a8d998")] +pub unsafe trait EXPERIMENTAL_IPluginAuthenticator: IUnknown { + fn EXPERIMENTAL_PluginMakeCredential( + &self, + request: *const ExperimentalWebAuthnPluginOperationRequest, + response: *mut ExperimentalWebAuthnPluginOperationResponse, + ) -> HRESULT; + fn EXPERIMENTAL_PluginGetAssertion( + &self, + request: *const ExperimentalWebAuthnPluginOperationRequest, + response: *mut ExperimentalWebAuthnPluginOperationResponse, + ) -> HRESULT; + fn EXPERIMENTAL_PluginCancelOperation( + &self, + request: *const ExperimentalWebAuthnPluginCancelOperationRequest, + ) -> HRESULT; +} + +#[implement(EXPERIMENTAL_IPluginAuthenticator)] +pub struct PluginAuthenticatorComObject; + +#[implement(IClassFactory)] +pub struct Factory(); + +impl EXPERIMENTAL_IPluginAuthenticator_Impl for PluginAuthenticatorComObject_Impl { + unsafe fn EXPERIMENTAL_PluginMakeCredential( + &self, + _request: *const ExperimentalWebAuthnPluginOperationRequest, + _response: *mut ExperimentalWebAuthnPluginOperationResponse, + ) -> HRESULT { + HRESULT(0) + } + + unsafe fn EXPERIMENTAL_PluginGetAssertion( + &self, + _request: *const ExperimentalWebAuthnPluginOperationRequest, + _response: *mut ExperimentalWebAuthnPluginOperationResponse, + ) -> HRESULT { + HRESULT(0) + } + + unsafe fn EXPERIMENTAL_PluginCancelOperation( + &self, + _request: *const ExperimentalWebAuthnPluginCancelOperationRequest, + ) -> HRESULT { + HRESULT(0) + } +} + +impl IClassFactory_Impl for Factory_Impl { + fn CreateInstance( + &self, + outer: Ref, + iid: *const GUID, + object: *mut *mut core::ffi::c_void, + ) -> Result<()> { + assert!(outer.is_null()); + let unknown: IInspectable = PluginAuthenticatorComObject.into(); + unsafe { unknown.query(iid, object).ok() } + } + + fn LockServer(&self, lock: BOOL) -> Result<()> { + assert!(lock.as_bool()); + Ok(()) + } +} diff --git a/apps/desktop/desktop_native/windows_plugin_authenticator/src/webauthn.rs b/apps/desktop/desktop_native/windows_plugin_authenticator/src/webauthn.rs new file mode 100644 index 00000000000..18c7563ffd8 --- /dev/null +++ b/apps/desktop/desktop_native/windows_plugin_authenticator/src/webauthn.rs @@ -0,0 +1,29 @@ +/* + This file exposes the functions and types defined here: https://github.com/microsoft/webauthn/blob/master/experimental/webauthn.h +*/ + +/// Used when adding a Windows plugin authenticator. +/// Header File Name: _EXPERIMENTAL_WEBAUTHN_PLUGIN_ADD_AUTHENTICATOR_OPTIONS +/// Header File Usage: EXPERIMENTAL_WebAuthNPluginAddAuthenticator() +#[repr(C)] +#[derive(Debug, Copy, Clone)] +pub struct ExperimentalWebAuthnPluginAddAuthenticatorOptions { + pub authenticator_name: *const u16, + pub com_clsid: *const u16, + pub rpid: *const u16, + pub light_theme_logo: *const u16, + pub dark_theme_logo: *const u16, + pub cbor_authenticator_info_byte_count: u32, + pub cbor_authenticator_info: *const u8, +} + +/// Used as a response type when adding a Windows plugin authenticator. +/// Header File Name: _EXPERIMENTAL_WEBAUTHN_PLUGIN_ADD_AUTHENTICATOR_RESPONSE +/// Header File Usage: EXPERIMENTAL_WebAuthNPluginAddAuthenticator() +/// EXPERIMENTAL_WebAuthNPluginFreeAddAuthenticatorResponse() +#[repr(C)] +#[derive(Debug, Copy, Clone)] +pub struct ExperimentalWebAuthnPluginAddAuthenticatorResponse { + pub plugin_operation_signing_key_byte_count: u32, + pub plugin_operation_signing_key: *mut u8, +} diff --git a/apps/desktop/electron-builder.json b/apps/desktop/electron-builder.json index 30d87c5c662..d51d9412d80 100644 --- a/apps/desktop/electron-builder.json +++ b/apps/desktop/electron-builder.json @@ -5,7 +5,7 @@ "productName": "Bitwarden", "appId": "com.bitwarden.desktop", "buildDependenciesFromSource": true, - "copyright": "Copyright © 2015-2024 Bitwarden Inc.", + "copyright": "Copyright © 2015-2025 Bitwarden Inc.", "directories": { "buildResources": "resources", "output": "dist", @@ -33,7 +33,7 @@ "gatekeeperAssess": false, "hardenedRuntime": true, "entitlements": "resources/entitlements.mac.plist", - "entitlementsInherit": "resources/entitlements.mac.plist", + "entitlementsInherit": "resources/entitlements.mac.inherit.plist", "extendInfo": { "ITSAppUsesNonExemptEncryption": false, "CFBundleLocalizations": [ @@ -67,6 +67,7 @@ ], "CFBundleDevelopmentRegion": "en" }, + "provisioningProfile": "bitwarden_desktop_developer_id.provisionprofile", "singleArchFiles": "node_modules/@bitwarden/desktop-napi/desktop_napi.darwin-*.node", "extraFiles": [ { @@ -78,7 +79,11 @@ "to": "MacOS/desktop_proxy.inherit" } ], - "signIgnore": ["MacOS/desktop_proxy", "MacOS/desktop_proxy.inherit"], + "signIgnore": [ + "MacOS/desktop_proxy", + "MacOS/desktop_proxy.inherit", + "Contents/Plugins/autofill-extension.appex" + ], "target": ["dmg", "zip"] }, "win": { @@ -137,7 +142,8 @@ "extendInfo": { "LSMinimumSystemVersion": "12", "ElectronTeamID": "LTZ2PFU5D6" - } + }, + "provisioningProfile": "bitwarden_desktop_appstore.provisionprofile" }, "nsisWeb": { "oneClick": false, diff --git a/apps/desktop/macos/Debug.xcconfig b/apps/desktop/macos/Debug.xcconfig new file mode 100644 index 00000000000..73d8cd871fb --- /dev/null +++ b/apps/desktop/macos/Debug.xcconfig @@ -0,0 +1,11 @@ +// +// Debug.xcconfig +// desktop +// +// Created by Nathan Ansel on 2/20/25. +// + +// Configuration settings file format documentation can be found at: +// https://help.apple.com/xcode/#/dev745c5c974 +CODE_SIGN_IDENTITY = Apple Development +PROVISIONING_PROFILE_SPECIFIER = Bitwarden Desktop Autofill Development 2024 diff --git a/apps/desktop/macos/ReleaseAppStore.xcconfig b/apps/desktop/macos/ReleaseAppStore.xcconfig new file mode 100644 index 00000000000..2b891bbfc81 --- /dev/null +++ b/apps/desktop/macos/ReleaseAppStore.xcconfig @@ -0,0 +1,11 @@ +// +// ReleaseAppStore.xcconfig +// desktop +// +// Created by Vince Grassia on 7/25/24. +// + +// Configuration settings file format documentation can be found at: +// https://help.apple.com/xcode/#/dev745c5c974 +CODE_SIGN_IDENTITY = 3rd Party Mac Developer Application +PROVISIONING_PROFILE_SPECIFIER = Bitwarden Desktop Autofill App Store 2024 diff --git a/apps/desktop/macos/ReleaseDeveloper.xcconfig b/apps/desktop/macos/ReleaseDeveloper.xcconfig new file mode 100644 index 00000000000..47a047cbcf3 --- /dev/null +++ b/apps/desktop/macos/ReleaseDeveloper.xcconfig @@ -0,0 +1,11 @@ +// +// ReleaseDeveloper.xcconfig +// desktop +// +// Created by Nathan Ansel on 2/20/25. +// + +// Configuration settings file format documentation can be found at: +// https://help.apple.com/xcode/#/dev745c5c974 +CODE_SIGN_IDENTITY = Developer ID Application +PROVISIONING_PROFILE_SPECIFIER = Bitwarden Desktop Autofill Extension Developer Dis diff --git a/apps/desktop/macos/desktop.xcodeproj/project.pbxproj b/apps/desktop/macos/desktop.xcodeproj/project.pbxproj index 2ac467f3289..ff257097f26 100644 --- a/apps/desktop/macos/desktop.xcodeproj/project.pbxproj +++ b/apps/desktop/macos/desktop.xcodeproj/project.pbxproj @@ -17,7 +17,9 @@ /* Begin PBXFileReference section */ 3368DB382C654B8100896B75 /* BitwardenMacosProviderFFI.xcframework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcframework; name = BitwardenMacosProviderFFI.xcframework; path = ../desktop_native/macos_provider/BitwardenMacosProviderFFI.xcframework; sourceTree = ""; }; 3368DB3A2C654F3800896B75 /* BitwardenMacosProvider.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BitwardenMacosProvider.swift; sourceTree = ""; }; - 968ED08A2C52A47200FFFEE6 /* Production.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Production.xcconfig; sourceTree = ""; }; + 968ED08A2C52A47200FFFEE6 /* ReleaseAppStore.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = ReleaseAppStore.xcconfig; sourceTree = ""; }; + D83832AB2D67B9AE003FB9F8 /* Debug.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Debug.xcconfig; sourceTree = ""; }; + D83832AD2D67B9D0003FB9F8 /* ReleaseDeveloper.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = ReleaseDeveloper.xcconfig; sourceTree = ""; }; E1DF713C2B342F6900F29026 /* autofill-extension.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = "autofill-extension.appex"; sourceTree = BUILT_PRODUCTS_DIR; }; E1DF713E2B342F6900F29026 /* AuthenticationServices.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AuthenticationServices.framework; path = System/Library/Frameworks/AuthenticationServices.framework; sourceTree = SDKROOT; }; E1DF71412B342F6900F29026 /* CredentialProviderViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CredentialProviderViewController.swift; sourceTree = ""; }; @@ -42,7 +44,9 @@ E1DF711D2B342E2800F29026 = { isa = PBXGroup; children = ( - 968ED08A2C52A47200FFFEE6 /* Production.xcconfig */, + D83832AB2D67B9AE003FB9F8 /* Debug.xcconfig */, + 968ED08A2C52A47200FFFEE6 /* ReleaseAppStore.xcconfig */, + D83832AD2D67B9D0003FB9F8 /* ReleaseDeveloper.xcconfig */, E1DF71402B342F6900F29026 /* autofill-extension */, E1DF713D2B342F6900F29026 /* Frameworks */, E1DF71272B342E2800F29026 /* Products */, @@ -166,8 +170,97 @@ /* End PBXVariantGroup section */ /* Begin XCBuildConfiguration section */ + D83832AE2D67BA84003FB9F8 /* ReleaseDeveloper */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = D83832AD2D67B9D0003FB9F8 /* ReleaseDeveloper.xcconfig */; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + CODE_SIGN_ENTITLEMENTS = "autofill-extension/autofill_extension.entitlements"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_USER_SCRIPT_SANDBOXING = YES; + GCC_C_LANGUAGE_STANDARD = gnu17; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + LOCALIZATION_PREFERS_STRING_CATALOGS = YES; + MACOSX_DEPLOYMENT_TARGET = 14.2; + MTL_ENABLE_DEBUG_INFO = NO; + MTL_FAST_MATH = YES; + SDKROOT = macosx; + SWIFT_COMPILATION_MODE = wholemodule; + }; + name = ReleaseDeveloper; + }; + D83832AF2D67BA84003FB9F8 /* ReleaseDeveloper */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = D83832AD2D67B9D0003FB9F8 /* ReleaseDeveloper.xcconfig */; + buildSettings = { + CODE_SIGN_ENTITLEMENTS = "autofill-extension/autofill_extension.entitlements"; + CODE_SIGN_IDENTITY = "Apple Development"; + "CODE_SIGN_IDENTITY[sdk=macosx*]" = "Mac Developer"; + CODE_SIGN_STYLE = Manual; + CURRENT_PROJECT_VERSION = 1; + DEVELOPMENT_TEAM = ""; + "DEVELOPMENT_TEAM[sdk=macosx*]" = LTZ2PFU5D6; + ENABLE_HARDENED_RUNTIME = YES; + GENERATE_INFOPLIST_FILE = YES; + INFOPLIST_FILE = "autofill-extension/Info.plist"; + INFOPLIST_KEY_CFBundleDisplayName = Bitwarden; + INFOPLIST_KEY_NSHumanReadableCopyright = ""; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/../Frameworks", + "@executable_path/../../../../Frameworks", + ); + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = "com.bitwarden.desktop.autofill-extension"; + PRODUCT_NAME = "$(TARGET_NAME)"; + PROVISIONING_PROFILE_SPECIFIER = "Bitwarden Desktop Autofill App Store 2024"; + SKIP_INSTALL = YES; + SWIFT_EMIT_LOC_STRINGS = YES; + SWIFT_VERSION = 5.0; + }; + name = ReleaseDeveloper; + }; E1DF71332B342E2900F29026 /* Debug */ = { isa = XCBuildConfiguration; + baseConfigurationReference = D83832AB2D67B9AE003FB9F8 /* Debug.xcconfig */; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; @@ -223,15 +316,16 @@ MACOSX_DEPLOYMENT_TARGET = 14.2; MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; MTL_FAST_MATH = YES; - ONLY_ACTIVE_ARCH = YES; + ONLY_ACTIVE_ARCH = NO; SDKROOT = macosx; SWIFT_ACTIVE_COMPILATION_CONDITIONS = "DEBUG $(inherited)"; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; }; name = Debug; }; - E1DF71342B342E2900F29026 /* Release */ = { + E1DF71342B342E2900F29026 /* ReleaseAppStore */ = { isa = XCBuildConfiguration; + baseConfigurationReference = 968ED08A2C52A47200FFFEE6 /* ReleaseAppStore.xcconfig */; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; @@ -284,10 +378,11 @@ SDKROOT = macosx; SWIFT_COMPILATION_MODE = wholemodule; }; - name = Release; + name = ReleaseAppStore; }; E1DF714C2B342F6900F29026 /* Debug */ = { isa = XCBuildConfiguration; + baseConfigurationReference = D83832AB2D67B9AE003FB9F8 /* Debug.xcconfig */; buildSettings = { CODE_SIGN_ENTITLEMENTS = "autofill-extension/autofill_extension.entitlements"; CODE_SIGN_IDENTITY = "Apple Development"; @@ -309,16 +404,16 @@ MARKETING_VERSION = 1.0; PRODUCT_BUNDLE_IDENTIFIER = "com.bitwarden.desktop.autofill-extension"; PRODUCT_NAME = "$(TARGET_NAME)"; - PROVISIONING_PROFILE_SPECIFIER = ""; - "PROVISIONING_PROFILE_SPECIFIER[sdk=macosx*]" = "Bitwarden Desktop Autofill Development 2024"; + PROVISIONING_PROFILE_SPECIFIER = "Bitwarden Desktop Autofill Development 2024"; SKIP_INSTALL = YES; SWIFT_EMIT_LOC_STRINGS = YES; SWIFT_VERSION = 5.0; }; name = Debug; }; - E1DF714D2B342F6900F29026 /* Release */ = { + E1DF714D2B342F6900F29026 /* ReleaseAppStore */ = { isa = XCBuildConfiguration; + baseConfigurationReference = 968ED08A2C52A47200FFFEE6 /* ReleaseAppStore.xcconfig */; buildSettings = { CODE_SIGN_ENTITLEMENTS = "autofill-extension/autofill_extension.entitlements"; CODE_SIGN_IDENTITY = "Apple Development"; @@ -340,13 +435,12 @@ MARKETING_VERSION = 1.0; PRODUCT_BUNDLE_IDENTIFIER = "com.bitwarden.desktop.autofill-extension"; PRODUCT_NAME = "$(TARGET_NAME)"; - PROVISIONING_PROFILE_SPECIFIER = ""; - "PROVISIONING_PROFILE_SPECIFIER[sdk=macosx*]" = "Bitwarden Desktop Autofill Development 2024"; + PROVISIONING_PROFILE_SPECIFIER = "Bitwarden Desktop Autofill App Store 2024"; SKIP_INSTALL = YES; SWIFT_EMIT_LOC_STRINGS = YES; SWIFT_VERSION = 5.0; }; - name = Release; + name = ReleaseAppStore; }; /* End XCBuildConfiguration section */ @@ -355,19 +449,21 @@ isa = XCConfigurationList; buildConfigurations = ( E1DF71332B342E2900F29026 /* Debug */, - E1DF71342B342E2900F29026 /* Release */, + E1DF71342B342E2900F29026 /* ReleaseAppStore */, + D83832AE2D67BA84003FB9F8 /* ReleaseDeveloper */, ); defaultConfigurationIsVisible = 0; - defaultConfigurationName = Release; + defaultConfigurationName = ReleaseAppStore; }; E1DF714E2B342F6900F29026 /* Build configuration list for PBXNativeTarget "autofill-extension" */ = { isa = XCConfigurationList; buildConfigurations = ( E1DF714C2B342F6900F29026 /* Debug */, - E1DF714D2B342F6900F29026 /* Release */, + E1DF714D2B342F6900F29026 /* ReleaseAppStore */, + D83832AF2D67BA84003FB9F8 /* ReleaseDeveloper */, ); defaultConfigurationIsVisible = 0; - defaultConfigurationName = Release; + defaultConfigurationName = ReleaseAppStore; }; /* End XCConfigurationList section */ }; diff --git a/apps/desktop/macos/production.xcconfig b/apps/desktop/macos/production.xcconfig deleted file mode 100644 index f06f2bf736e..00000000000 --- a/apps/desktop/macos/production.xcconfig +++ /dev/null @@ -1,11 +0,0 @@ -// -// Production.xcconfig -// desktop -// -// Created by Vince Grassia on 7/25/24. -// - -// Configuration settings file format documentation can be found at: -// https://help.apple.com/xcode/#/dev745c5c974 -CODE_SIGN_IDENTITY[sdk=macosx*] = 3rd Party Mac Developer Application -PROVISIONING_PROFILE_SPECIFIER[sdk=macosx*] = Bitwarden Desktop Autofill App Store 2024 diff --git a/apps/desktop/native-messaging-test-runner/package-lock.json b/apps/desktop/native-messaging-test-runner/package-lock.json index 02a5e850401..d506e109e94 100644 --- a/apps/desktop/native-messaging-test-runner/package-lock.json +++ b/apps/desktop/native-messaging-test-runner/package-lock.json @@ -13,11 +13,11 @@ "@bitwarden/node": "file:../../../libs/node", "module-alias": "2.2.3", "ts-node": "10.9.2", - "uuid": "11.0.5", + "uuid": "11.1.0", "yargs": "17.7.2" }, "devDependencies": { - "@types/node": "22.10.7", + "@types/node": "22.14.1", "typescript": "5.4.2" } }, @@ -101,12 +101,12 @@ "license": "MIT" }, "node_modules/@types/node": { - "version": "22.10.7", - "resolved": "https://registry.npmjs.org/@types/node/-/node-22.10.7.tgz", - "integrity": "sha512-V09KvXxFiutGp6B7XkpaDXlNadZxrzajcY50EuoLIpQ6WWYCSvf19lVIazzfIzQvhUN2HjX12spLojTnhuKlGg==", + "version": "22.14.1", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.14.1.tgz", + "integrity": "sha512-u0HuPQwe/dHrItgHHpmw3N2fYCR6x4ivMNbPHRkBVP4CvN+kiRrKHWk3i8tXiO/joPwXLMYvF9TTF0eqgHIuOw==", "license": "MIT", "dependencies": { - "undici-types": "~6.20.0" + "undici-types": "~6.21.0" } }, "node_modules/acorn": { @@ -347,15 +347,15 @@ } }, "node_modules/undici-types": { - "version": "6.20.0", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.20.0.tgz", - "integrity": "sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg==", + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", + "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==", "license": "MIT" }, "node_modules/uuid": { - "version": "11.0.5", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-11.0.5.tgz", - "integrity": "sha512-508e6IcKLrhxKdBbcA2b4KQZlLVp2+J5UwQ6F7Drckkc5N9ZJwFa4TgWtsww9UG8fGHbm6gbV19TdM5pQ4GaIA==", + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-11.1.0.tgz", + "integrity": "sha512-0/A9rDy9P7cJ+8w1c9WD9V//9Wj15Ce2MPz8Ri6032usz+NfePxx5AcN3bN+r6ZL6jEo066/yNYB3tn4pQEx+A==", "funding": [ "https://github.com/sponsors/broofa", "https://github.com/sponsors/ctavan" diff --git a/apps/desktop/native-messaging-test-runner/package.json b/apps/desktop/native-messaging-test-runner/package.json index 0df272c142f..f67ab259d3b 100644 --- a/apps/desktop/native-messaging-test-runner/package.json +++ b/apps/desktop/native-messaging-test-runner/package.json @@ -18,11 +18,11 @@ "@bitwarden/node": "file:../../../libs/node", "module-alias": "2.2.3", "ts-node": "10.9.2", - "uuid": "11.0.5", + "uuid": "11.1.0", "yargs": "17.7.2" }, "devDependencies": { - "@types/node": "22.10.7", + "@types/node": "22.14.1", "typescript": "5.4.2" }, "_moduleAliases": { diff --git a/apps/desktop/package.json b/apps/desktop/package.json index ae34deee5b8..21892cd1df8 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": "2025.4.1", + "version": "2025.5.0", "keywords": [ "bitwarden", "password", @@ -23,7 +23,9 @@ "build:dev": "concurrently -n Main,Rend -c yellow,cyan \"npm run build:main:dev\" \"npm run build:renderer:dev\"", "build:preload": "cross-env NODE_ENV=production webpack --config webpack.preload.js", "build:preload:watch": "cross-env NODE_ENV=production webpack --config webpack.preload.js --watch", - "build:macos-extension": "./desktop_native/macos_provider/build.sh && node scripts/build-macos-extension.js", + "build:macos-extension:mac": "./desktop_native/macos_provider/build.sh && node scripts/build-macos-extension.js mac", + "build:macos-extension:mas": "./desktop_native/macos_provider/build.sh && node scripts/build-macos-extension.js mas", + "build:macos-extension:masdev": "./desktop_native/macos_provider/build.sh && node scripts/build-macos-extension.js mas-dev", "build:main": "cross-env NODE_ENV=production webpack --config webpack.main.js", "build:main:dev": "npm run build-native && cross-env NODE_ENV=development webpack --config webpack.main.js", "build:main:watch": "npm run build-native && cross-env NODE_ENV=development webpack --config webpack.main.js --watch", @@ -38,17 +40,21 @@ "pack:lin": "npm run clean:dist && electron-builder --linux --x64 -p never && export SNAP_FILE=$(realpath ./dist/bitwarden_*.snap) && unsquashfs -d ./dist/tmp-snap/ $SNAP_FILE && mkdir -p ./dist/tmp-snap/meta/polkit/ && cp ./resources/com.bitwarden.desktop.policy ./dist/tmp-snap/meta/polkit/polkit.com.bitwarden.desktop.policy && rm $SNAP_FILE && snapcraft pack ./dist/tmp-snap/ && mv ./*.snap ./dist/ && rm -rf ./dist/tmp-snap/", "pack:lin:arm64": "npm run clean:dist && electron-builder --dir -p never && tar -czvf ./dist/bitwarden_desktop_arm64.tar.gz -C ./dist/linux-arm64-unpacked/ .", "pack:mac": "npm run clean:dist && electron-builder --mac --universal -p never", + "pack:mac:with-extension": "npm run clean:dist && npm run build:macos-extension:mac && electron-builder --mac --universal -p never", "pack:mac:arm64": "npm run clean:dist && electron-builder --mac --arm64 -p never", "pack:mac:mas": "npm run clean:dist && electron-builder --mac mas --universal -p never", + "pack:mac:mas:with-extension": "npm run clean:dist && npm run build:macos-extension:mas && electron-builder --mac mas --universal -p never", "pack:mac:masdev": "npm run clean:dist && electron-builder --mac mas-dev --universal -p never", - "pack:mac:masdev:with-extension": "npm run clean:dist && npm run build:macos-extension && electron-builder --mac mas-dev --universal -p never", + "pack:mac:masdev:with-extension": "npm run clean:dist && npm run build:macos-extension:masdev && electron-builder --mac mas-dev --universal -p never", "pack:win": "npm run clean:dist && electron-builder --win --x64 --arm64 --ia32 -p never -c.win.certificateSubjectName=\"8bit Solutions LLC\"", "pack:win:ci": "npm run clean:dist && electron-builder --win --x64 --arm64 --ia32 -p never", "dist:dir": "npm run build && npm run pack:dir", "dist:lin": "npm run build && npm run pack:lin", "dist:lin:arm64": "npm run build && npm run pack:lin:arm64", "dist:mac": "npm run build && npm run pack:mac", + "dist:mac:with-extension": "npm run build && npm run pack:mac:with-extension", "dist:mac:mas": "npm run build && npm run pack:mac:mas", + "dist:mac:mas:with-extension": "npm run build && npm run pack:mac:mas:with-extension", "dist:mac:masdev": "npm run build && npm run pack:mac:masdev", "dist:mac:masdev:with-extension": "npm run build && npm run pack:mac:masdev:with-extension", "dist:win": "npm run build && npm run pack:win", diff --git a/apps/desktop/resources/entitlements.mac.inherit.plist b/apps/desktop/resources/entitlements.mac.inherit.plist new file mode 100644 index 00000000000..d35e43ae588 --- /dev/null +++ b/apps/desktop/resources/entitlements.mac.inherit.plist @@ -0,0 +1,8 @@ + + + + + com.apple.security.cs.allow-jit + + + diff --git a/apps/desktop/resources/entitlements.mac.plist b/apps/desktop/resources/entitlements.mac.plist index e273bcc7eca..fe49256d71c 100644 --- a/apps/desktop/resources/entitlements.mac.plist +++ b/apps/desktop/resources/entitlements.mac.plist @@ -2,11 +2,13 @@ - com.apple.security.cs.allow-jit - - + com.apple.security.cs.allow-jit + diff --git a/apps/desktop/resources/entitlements.mas.inherit.plist b/apps/desktop/resources/entitlements.mas.inherit.plist index 7e957fce7ce..fca5f02d52d 100644 --- a/apps/desktop/resources/entitlements.mas.inherit.plist +++ b/apps/desktop/resources/entitlements.mas.inherit.plist @@ -8,9 +8,5 @@ com.apple.security.cs.allow-jit - diff --git a/apps/desktop/resources/entitlements.mas.plist b/apps/desktop/resources/entitlements.mas.plist index f3bc20011ff..3ebd56f0fd7 100644 --- a/apps/desktop/resources/entitlements.mas.plist +++ b/apps/desktop/resources/entitlements.mas.plist @@ -6,6 +6,8 @@ LTZ2PFU5D6.com.bitwarden.desktop com.apple.developer.team-identifier LTZ2PFU5D6 + com.apple.developer.authentication-services.autofill-credential-provider + com.apple.security.app-sandbox com.apple.security.application-groups @@ -18,10 +20,6 @@ com.apple.security.device.usb - com.apple.security.temporary-exception.files.home-relative-path.read-write /Library/Application Support/Mozilla/NativeMessagingHosts/ diff --git a/apps/desktop/scripts/after-sign.js b/apps/desktop/scripts/after-sign.js index 20c24c8a76b..7c9ad381dc2 100644 --- a/apps/desktop/scripts/after-sign.js +++ b/apps/desktop/scripts/after-sign.js @@ -16,7 +16,7 @@ async function run(context) { const appPath = `${context.appOutDir}/${appName}.app`; const macBuild = context.electronPlatformName === "darwin"; const copySafariExtension = ["darwin", "mas"].includes(context.electronPlatformName); - const copyAutofillExtension = ["mas"].includes(context.electronPlatformName); + const copyAutofillExtension = ["darwin", "mas"].includes(context.electronPlatformName); let shouldResign = false; diff --git a/apps/desktop/scripts/build-macos-extension.js b/apps/desktop/scripts/build-macos-extension.js index 649fe3b6736..dcc1725d50f 100644 --- a/apps/desktop/scripts/build-macos-extension.js +++ b/apps/desktop/scripts/build-macos-extension.js @@ -6,14 +6,19 @@ const fse = require("fs-extra"); const paths = { macosBuild: "./macos/build", - extensionBuild: "./macos/build/Release/autofill-extension.appex", + extensionBuildDebug: "./macos/build/Debug/autofill-extension.appex", + extensionBuildReleaseAppStore: "./macos/build/ReleaseAppStore/autofill-extension.appex", + extensionBuildReleaseDeveloper: "./macos/build/ReleaseDeveloper/autofill-extension.appex", extensionDistDir: "./macos/dist", extensionDist: "./macos/dist/autofill-extension.appex", macOsProject: "./macos/desktop.xcodeproj", - macOsConfig: "./macos/production.xcconfig", }; +exports.default = buildMacOs; + async function buildMacOs() { + console.log("### Building Autofill Extension"); + if (fse.existsSync(paths.macosBuild)) { fse.removeSync(paths.macosBuild); } @@ -22,15 +27,50 @@ async function buildMacOs() { fse.removeSync(paths.extensionDistDir); } + let configuration; + let codeSignIdentity; + let provisioningProfileSpecifier; + let buildDirectory; + const configurationArgument = process.argv[2]; + if (configurationArgument !== undefined) { + // Use the configuration passed in to determine the configuration file. + if (configurationArgument == "mas-dev") { + configuration = "Debug"; + codeSignIdentity = "Apple Development"; + provisioningProfileSpecifier = "Bitwarden Desktop Autofill Development 2024"; + buildDirectory = paths.extensionBuildDebug; + } else if (configurationArgument == "mas") { + configuration = "ReleaseAppStore"; + codeSignIdentity = "3rd Party Mac Developer Application"; + provisioningProfileSpecifier = "Bitwarden Desktop Autofill App Store 2024"; + buildDirectory = paths.extensionBuildReleaseAppStore; + } else if (configurationArgument == "mac") { + configuration = "ReleaseDeveloper"; + codeSignIdentity = "Developer ID Application"; + provisioningProfileSpecifier = "Bitwarden Desktop Autofill Extension Developer Dis"; + buildDirectory = paths.extensionBuildReleaseDeveloper; + } else { + console.log("### Unable to determine configuration, skipping Autofill Extension build"); + return; + } + } else { + console.log("### No configuration argument found, skipping Autofill Extension build"); + return; + } + const proc = child.spawn("xcodebuild", [ "-project", paths.macOsProject, "-alltargets", "-configuration", - "Release", - // Uncomment when signing is fixed - // "-xcconfig", - // paths.macOsConfig, + configuration, + "CODE_SIGN_INJECT_BASE_ENTITLEMENTS=NO", + "OTHER_CODE_SIGN_FLAGS='--timestamp'", + + // While these arguments are defined in the `configuration` file above, xcodebuild has a bug in it currently that requires these arguments + // be explicitly defined in this call. + `CODE_SIGN_IDENTITY=${codeSignIdentity}`, + `PROVISIONING_PROFILE_SPECIFIER=${provisioningProfileSpecifier}`, ]); stdOutProc(proc); await new Promise((resolve, reject) => @@ -45,7 +85,8 @@ async function buildMacOs() { ); fse.mkdirSync(paths.extensionDistDir); - fse.copySync(paths.extensionBuild, paths.extensionDist); + fse.copySync(buildDirectory, paths.extensionDist); + // Delete the build dir, otherwise MacOS will load the extension from there instead of the Bitwarden.app bundle fse.removeSync(paths.macosBuild); } diff --git a/apps/desktop/src/app/app-routing.module.ts b/apps/desktop/src/app/app-routing.module.ts index 0c6bc730c2c..00463152a95 100644 --- a/apps/desktop/src/app/app-routing.module.ts +++ b/apps/desktop/src/app/app-routing.module.ts @@ -14,6 +14,7 @@ import { tdeDecryptionRequiredGuard, unauthGuardFn, } from "@bitwarden/angular/auth/guards"; +import { featureFlaggedRoute } from "@bitwarden/angular/platform/utils/feature-flagged-route"; import { NewDeviceVerificationNoticeGuard } from "@bitwarden/angular/vault/guards"; import { AnonLayoutWrapperComponent, @@ -41,6 +42,7 @@ import { NewDeviceVerificationComponent, DeviceVerificationIcon, } from "@bitwarden/auth/angular"; +import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum"; import { LockComponent } from "@bitwarden/key-management-ui"; import { NewDeviceVerificationNoticePageOneComponent, @@ -53,6 +55,7 @@ import { maxAccountsGuardFn } from "../auth/guards/max-accounts.guard"; import { SetPasswordComponent } from "../auth/set-password.component"; import { UpdateTempPasswordComponent } from "../auth/update-temp-password.component"; import { RemovePasswordComponent } from "../key-management/key-connector/remove-password.component"; +import { VaultV2Component } from "../vault/app/vault/vault-v2.component"; import { VaultComponent } from "../vault/app/vault/vault.component"; import { Fido2PlaceholderComponent } from "./components/fido2placeholder.component"; @@ -132,11 +135,15 @@ const routes: Routes = [ }, ], }, - { - path: "vault", - component: VaultComponent, - canActivate: [authGuard, NewDeviceVerificationNoticeGuard], - }, + ...featureFlaggedRoute({ + defaultComponent: VaultComponent, + flaggedComponent: VaultV2Component, + featureFlag: FeatureFlag.PM18520_UpdateDesktopCipherForm, + routeOptions: { + path: "vault", + canActivate: [authGuard, NewDeviceVerificationNoticeGuard], + }, + }), { path: "accessibility-cookie", component: AccessibilityCookieComponent }, { path: "set-password", component: SetPasswordComponent }, { @@ -359,7 +366,7 @@ const routes: Routes = [ imports: [ RouterModule.forRoot(routes, { useHash: true, - /*enableTracing: true,*/ + // enableTracing: true, }), ], exports: [RouterModule], diff --git a/apps/desktop/src/app/app.module.ts b/apps/desktop/src/app/app.module.ts index b892324a979..15ab4350bbc 100644 --- a/apps/desktop/src/app/app.module.ts +++ b/apps/desktop/src/app/app.module.ts @@ -4,11 +4,11 @@ import "zone.js"; import "../platform/app/locales"; import { NgModule } from "@angular/core"; +import { BrowserAnimationsModule } from "@angular/platform-browser/animations"; import { ColorPasswordCountPipe } from "@bitwarden/angular/pipes/color-password-count.pipe"; import { ColorPasswordPipe } from "@bitwarden/angular/pipes/color-password.pipe"; import { CalloutModule, DialogModule } from "@bitwarden/components"; -import { DecryptionFailureDialogComponent } from "@bitwarden/vault"; import { AccessibilityCookieComponent } from "../auth/accessibility-cookie.component"; import { DeleteAccountComponent } from "../auth/delete-account.component"; @@ -27,6 +27,7 @@ import { PasswordHistoryComponent } from "../vault/app/vault/password-history.co import { ShareComponent } from "../vault/app/vault/share.component"; import { VaultFilterModule } from "../vault/app/vault/vault-filter/vault-filter.module"; import { VaultItemsComponent } from "../vault/app/vault/vault-items.component"; +import { VaultV2Component } from "../vault/app/vault/vault-v2.component"; import { VaultComponent } from "../vault/app/vault/vault.component"; import { ViewCustomFieldsComponent } from "../vault/app/vault/view-custom-fields.component"; import { ViewComponent } from "../vault/app/vault/view.component"; @@ -44,6 +45,8 @@ import { SharedModule } from "./shared/shared.module"; @NgModule({ imports: [ + BrowserAnimationsModule, + SharedModule, AppRoutingModule, VaultFilterModule, @@ -52,8 +55,8 @@ import { SharedModule } from "./shared/shared.module"; CalloutModule, DeleteAccountComponent, UserVerificationComponent, - DecryptionFailureDialogComponent, NavComponent, + VaultV2Component, ], declarations: [ AccessibilityCookieComponent, @@ -62,7 +65,6 @@ import { SharedModule } from "./shared/shared.module"; AddEditCustomFieldsComponent, AppComponent, AttachmentsComponent, - VaultItemsComponent, CollectionsComponent, ColorPasswordPipe, ColorPasswordCountPipe, @@ -77,9 +79,10 @@ import { SharedModule } from "./shared/shared.module"; ShareComponent, UpdateTempPasswordComponent, VaultComponent, + VaultItemsComponent, VaultTimeoutInputComponent, - ViewComponent, ViewCustomFieldsComponent, + ViewComponent, ], providers: [SshAgentService], bootstrap: [AppComponent], diff --git a/apps/desktop/src/app/shared/shared.module.ts b/apps/desktop/src/app/shared/shared.module.ts index e08d29c4d12..6eed4a197f3 100644 --- a/apps/desktop/src/app/shared/shared.module.ts +++ b/apps/desktop/src/app/shared/shared.module.ts @@ -2,11 +2,9 @@ import { A11yModule } from "@angular/cdk/a11y"; import { DragDropModule } from "@angular/cdk/drag-drop"; import { OverlayModule } from "@angular/cdk/overlay"; import { ScrollingModule } from "@angular/cdk/scrolling"; -import { DatePipe } from "@angular/common"; +import { CommonModule, DatePipe } from "@angular/common"; import { NgModule } from "@angular/core"; import { FormsModule, ReactiveFormsModule } from "@angular/forms"; -import { BrowserModule } from "@angular/platform-browser"; -import { BrowserAnimationsModule } from "@angular/platform-browser/animations"; import { JslibModule } from "@bitwarden/angular/jslib.module"; @@ -15,9 +13,8 @@ import { ServicesModule } from "../services/services.module"; @NgModule({ imports: [ + CommonModule, A11yModule, - BrowserAnimationsModule, - BrowserModule, DragDropModule, FormsModule, JslibModule, @@ -28,9 +25,8 @@ import { ServicesModule } from "../services/services.module"; ], declarations: [AvatarComponent], exports: [ + CommonModule, A11yModule, - BrowserAnimationsModule, - BrowserModule, DatePipe, DragDropModule, FormsModule, diff --git a/apps/desktop/src/key-management/biometrics/main-biometrics.service.spec.ts b/apps/desktop/src/key-management/biometrics/main-biometrics.service.spec.ts index 88dd8c60ed5..09a4dcef4b3 100644 --- a/apps/desktop/src/key-management/biometrics/main-biometrics.service.spec.ts +++ b/apps/desktop/src/key-management/biometrics/main-biometrics.service.spec.ts @@ -300,7 +300,7 @@ describe("MainBiometricsService", function () { expect(userKey).not.toBeNull(); expect(userKey!.keyB64).toBe(biometricKey); - expect(userKey!.encType).toBe(EncryptionType.AesCbc256_HmacSha256_B64); + expect(userKey!.inner().type).toBe(EncryptionType.AesCbc256_HmacSha256_B64); expect(osBiometricsService.getBiometricKey).toHaveBeenCalledWith( "Bitwarden_biometric", `${userId}_user_biometric`, diff --git a/apps/desktop/src/key-management/biometrics/os-biometrics-windows.service.ts b/apps/desktop/src/key-management/biometrics/os-biometrics-windows.service.ts index cd8c94329bc..53647549295 100644 --- a/apps/desktop/src/key-management/biometrics/os-biometrics-windows.service.ts +++ b/apps/desktop/src/key-management/biometrics/os-biometrics-windows.service.ts @@ -1,5 +1,7 @@ import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; +import { EncryptionType } from "@bitwarden/common/platform/enums"; +import { Utils } from "@bitwarden/common/platform/misc/utils"; import { EncString } from "@bitwarden/common/platform/models/domain/enc-string"; import { SymmetricCryptoKey } from "@bitwarden/common/platform/models/domain/symmetric-crypto-key"; import { biometrics, passwords } from "@bitwarden/desktop-napi"; @@ -218,7 +220,13 @@ export default class OsBiometricsServiceWindows implements OsBiometricService { symmetricKey: SymmetricCryptoKey, clientKeyPartB64: string | undefined, ): biometrics.KeyMaterial { - const key = symmetricKey?.macKeyB64 ?? symmetricKey?.keyB64; + let key = null; + const innerKey = symmetricKey.inner(); + if (innerKey.type === EncryptionType.AesCbc256_HmacSha256_B64) { + key = Utils.fromBufferToB64(innerKey.authenticationKey); + } else { + key = Utils.fromBufferToB64(innerKey.encryptionKey); + } const result = { osKeyPartB64: key, diff --git a/apps/desktop/src/key-management/lock/services/desktop-lock-component.service.spec.ts b/apps/desktop/src/key-management/lock/services/desktop-lock-component.service.spec.ts index 6bfbc803e87..6772af4f905 100644 --- a/apps/desktop/src/key-management/lock/services/desktop-lock-component.service.spec.ts +++ b/apps/desktop/src/key-management/lock/services/desktop-lock-component.service.spec.ts @@ -104,6 +104,22 @@ describe("DesktopLockComponentService", () => { }); }); + describe("popOutBrowserExtension", () => { + it("throws platform not supported error", () => { + expect(() => service.popOutBrowserExtension()).toThrow( + "Method not supported on this platform.", + ); + }); + }); + + describe("closeBrowserExtensionPopout", () => { + it("throws platform not supported error", () => { + expect(() => service.closeBrowserExtensionPopout()).toThrow( + "Method not supported on this platform.", + ); + }); + }); + describe("isWindowVisible", () => { it("returns the window visibility", async () => { isWindowVisibleMock.mockReturnValue(true); diff --git a/apps/desktop/src/key-management/lock/services/desktop-lock-component.service.ts b/apps/desktop/src/key-management/lock/services/desktop-lock-component.service.ts index 72df9336ea2..5cb3803930d 100644 --- a/apps/desktop/src/key-management/lock/services/desktop-lock-component.service.ts +++ b/apps/desktop/src/key-management/lock/services/desktop-lock-component.service.ts @@ -27,6 +27,14 @@ export class DesktopLockComponentService implements LockComponentService { return null; } + popOutBrowserExtension(): Promise { + throw new Error("Method not supported on this platform."); + } + + closeBrowserExtensionPopout(): void { + throw new Error("Method not supported on this platform."); + } + async isWindowVisible(): Promise { return ipc.platform.isWindowVisible(); } diff --git a/apps/desktop/src/locales/af/messages.json b/apps/desktop/src/locales/af/messages.json index c75fdc4db81..f1c510893ef 100644 --- a/apps/desktop/src/locales/af/messages.json +++ b/apps/desktop/src/locales/af/messages.json @@ -393,6 +393,64 @@ "authenticatorKeyTotp": { "message": "Waarmerksleutel (TOTP)" }, + "authenticatorKey": { + "message": "Authenticator key" + }, + "autofillOptions": { + "message": "Autofill options" + }, + "websiteUri": { + "message": "Website (URI)" + }, + "websiteUriCount": { + "message": "Website (URI) $COUNT$", + "description": "Label for an input field that contains a website URI. The input field is part of a list of fields, and the count indicates the position of the field in the list.", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + } + } + }, + "websiteAdded": { + "message": "Website added" + }, + "addWebsite": { + "message": "Add website" + }, + "deleteWebsite": { + "message": "Delete website" + }, + "owner": { + "message": "Owner" + }, + "addField": { + "message": "Add field" + }, + "fieldType": { + "message": "Field type" + }, + "fieldLabel": { + "message": "Field label" + }, + "add": { + "message": "Add" + }, + "textHelpText": { + "message": "Use text fields for data like security questions" + }, + "hiddenHelpText": { + "message": "Use hidden fields for sensitive data like a password" + }, + "checkBoxHelpText": { + "message": "Use checkboxes if you'd like to autofill a form's checkbox, like a remember email" + }, + "linkedHelpText": { + "message": "Use a linked field when you are experiencing autofill issues for a specific website." + }, + "linkedLabelHelpText": { + "message": "Enter the the field's html id, name, aria-label, or placeholder." + }, "folder": { "message": "Vouer" }, @@ -418,6 +476,9 @@ "message": "Gekoppel", "description": "This describes a field that is 'linked' (related) to another field." }, + "cfTypeCheckbox": { + "message": "Checkbox" + }, "linkedValue": { "message": "Gekoppelde waarde", "description": "This describes a value that is 'linked' (related) to another value." @@ -1915,6 +1976,43 @@ } } }, + "cardDetails": { + "message": "Card details" + }, + "cardBrandDetails": { + "message": "$BRAND$ details", + "placeholders": { + "brand": { + "content": "$1", + "example": "Visa" + } + } + }, + "learnMoreAboutAuthenticators": { + "message": "Learn more about authenticators" + }, + "copyTOTP": { + "message": "Copy Authenticator key (TOTP)" + }, + "totpHelperTitle": { + "message": "Make 2-step verification seamless" + }, + "totpHelper": { + "message": "Bitwarden can store and fill 2-step verification codes. Copy and paste the key into this field." + }, + "totpHelperWithCapture": { + "message": "Bitwarden can store and fill 2-step verification codes. Select the camera icon to take a screenshot of this website's authenticator QR code, or copy and paste the key into this field." + }, + "premium": { + "message": "Premium", + "description": "Premium membership" + }, + "cardExpiredTitle": { + "message": "Expired card" + }, + "cardExpiredMessage": { + "message": "If you've renewed it, update the card's information" + }, "verificationRequired": { "message": "Verification required", "description": "Default title for the user verification dialog." @@ -2084,6 +2182,15 @@ "personalOwnershipPolicyInEffectImports": { "message": "An organization policy has blocked importing items into your individual vault." }, + "personalDetails": { + "message": "Personal details" + }, + "identification": { + "message": "Identification" + }, + "contactInfo": { + "message": "Contact information" + }, "allSends": { "message": "Alle Sends", "description": "'Sends' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." @@ -2518,6 +2625,9 @@ "generateEmail": { "message": "Generate email" }, + "usernameGenerator": { + "message": "Username generator" + }, "spinboxBoundariesHint": { "message": "Value must be between $MIN$ and $MAX$.", "description": "Explains spin box minimum and maximum values to the user", @@ -3212,6 +3322,9 @@ "followTheStepsBelowToFinishLoggingIn": { "message": "Follow the steps below to finish logging in." }, + "followTheStepsBelowToFinishLoggingInWithSecurityKey": { + "message": "Follow the steps below to finish logging in with your security key." + }, "launchDuo": { "message": "Launch Duo in Browser" }, @@ -3436,6 +3549,17 @@ "ssoError": { "message": "No free ports could be found for the sso login." }, + "securePasswordGenerated": { + "message": "Secure password generated! Don't forget to also update your password on the website." + }, + "useGeneratorHelpTextPartOne": { + "message": "Use the generator", + "description": "This will be used as part of a larger sentence, broken up to include the generator icon. The full sentence will read 'Use the generator [GENERATOR_ICON] to create a strong unique password'" + }, + "useGeneratorHelpTextPartTwo": { + "message": "to create a strong unique password", + "description": "This will be used as part of a larger sentence, broken up to include the generator icon. The full sentence will read 'Use the generator [GENERATOR_ICON] to create a strong unique password'" + }, "biometricsStatusHelptextUnlockNeeded": { "message": "Biometric unlock is unavailable because PIN or password unlock is required first." }, @@ -3514,6 +3638,27 @@ "setupTwoStepLogin": { "message": "Set up two-step login" }, + "itemDetails": { + "message": "Item details" + }, + "itemName": { + "message": "Item name" + }, + "loginCredentials": { + "message": "Login credentials" + }, + "additionalOptions": { + "message": "Additional options" + }, + "itemHistory": { + "message": "Item history" + }, + "lastEdited": { + "message": "Last edited" + }, + "upload": { + "message": "Upload" + }, "newDeviceVerificationNoticeContentPage1": { "message": "Bitwarden will send a code to your account email to verify logins from new devices starting in February 2025." }, @@ -3567,5 +3712,35 @@ }, "move": { "message": "Move" + }, + "newLoginNudgeTitle": { + "message": "Save time with autofill" + }, + "newLoginNudgeBody": { + "message": "Include a Website so this login appears as an autofill suggestion." + }, + "newCardNudgeTitle": { + "message": "Seamless online checkout" + }, + "newCardNudgeBody": { + "message": "With cards, easily autofill payment forms securely and accurately." + }, + "newIdentityNudgeTitle": { + "message": "Simplify creating accounts" + }, + "newIdentityNudgeBody": { + "message": "With identities, quickly autofill long registration or contact forms." + }, + "newNoteNudgeTitle": { + "message": "Keep your sensitive data safe" + }, + "newNoteNudgeBody": { + "message": "With notes, securely store sensitive data like banking or insurance details." + }, + "newSshNudgeTitle": { + "message": "Developer-friendly SSH access" + }, + "newSshNudgeBody": { + "message": "Store your keys and connect with the SSH agent for fast, encrypted authentication." } } diff --git a/apps/desktop/src/locales/ar/messages.json b/apps/desktop/src/locales/ar/messages.json index 8223cf9d2b4..696bb21087f 100644 --- a/apps/desktop/src/locales/ar/messages.json +++ b/apps/desktop/src/locales/ar/messages.json @@ -393,6 +393,64 @@ "authenticatorKeyTotp": { "message": "مفتاح المصادقة (TOTP)" }, + "authenticatorKey": { + "message": "Authenticator key" + }, + "autofillOptions": { + "message": "Autofill options" + }, + "websiteUri": { + "message": "Website (URI)" + }, + "websiteUriCount": { + "message": "Website (URI) $COUNT$", + "description": "Label for an input field that contains a website URI. The input field is part of a list of fields, and the count indicates the position of the field in the list.", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + } + } + }, + "websiteAdded": { + "message": "Website added" + }, + "addWebsite": { + "message": "Add website" + }, + "deleteWebsite": { + "message": "Delete website" + }, + "owner": { + "message": "Owner" + }, + "addField": { + "message": "Add field" + }, + "fieldType": { + "message": "Field type" + }, + "fieldLabel": { + "message": "Field label" + }, + "add": { + "message": "Add" + }, + "textHelpText": { + "message": "Use text fields for data like security questions" + }, + "hiddenHelpText": { + "message": "Use hidden fields for sensitive data like a password" + }, + "checkBoxHelpText": { + "message": "Use checkboxes if you'd like to autofill a form's checkbox, like a remember email" + }, + "linkedHelpText": { + "message": "Use a linked field when you are experiencing autofill issues for a specific website." + }, + "linkedLabelHelpText": { + "message": "Enter the the field's html id, name, aria-label, or placeholder." + }, "folder": { "message": "المجلد" }, @@ -418,6 +476,9 @@ "message": "مرتبط", "description": "This describes a field that is 'linked' (related) to another field." }, + "cfTypeCheckbox": { + "message": "Checkbox" + }, "linkedValue": { "message": "القيمة المرتبطة", "description": "This describes a value that is 'linked' (related) to another value." @@ -1915,6 +1976,43 @@ } } }, + "cardDetails": { + "message": "Card details" + }, + "cardBrandDetails": { + "message": "$BRAND$ details", + "placeholders": { + "brand": { + "content": "$1", + "example": "Visa" + } + } + }, + "learnMoreAboutAuthenticators": { + "message": "Learn more about authenticators" + }, + "copyTOTP": { + "message": "Copy Authenticator key (TOTP)" + }, + "totpHelperTitle": { + "message": "Make 2-step verification seamless" + }, + "totpHelper": { + "message": "Bitwarden can store and fill 2-step verification codes. Copy and paste the key into this field." + }, + "totpHelperWithCapture": { + "message": "Bitwarden can store and fill 2-step verification codes. Select the camera icon to take a screenshot of this website's authenticator QR code, or copy and paste the key into this field." + }, + "premium": { + "message": "Premium", + "description": "Premium membership" + }, + "cardExpiredTitle": { + "message": "Expired card" + }, + "cardExpiredMessage": { + "message": "If you've renewed it, update the card's information" + }, "verificationRequired": { "message": "التحقق مطلوب", "description": "Default title for the user verification dialog." @@ -2084,6 +2182,15 @@ "personalOwnershipPolicyInEffectImports": { "message": "لقد حالت سياسة المؤسسة دون استيراد العناصر إلى خزانتك الشخصية." }, + "personalDetails": { + "message": "Personal details" + }, + "identification": { + "message": "Identification" + }, + "contactInfo": { + "message": "Contact information" + }, "allSends": { "message": "كل الإرسالات", "description": "'Sends' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." @@ -2518,6 +2625,9 @@ "generateEmail": { "message": "إنشاء بريد إلكتروني" }, + "usernameGenerator": { + "message": "Username generator" + }, "spinboxBoundariesHint": { "message": "يجب أن تكون القيمة بين $MIN$ و $MAX$.", "description": "Explains spin box minimum and maximum values to the user", @@ -3212,6 +3322,9 @@ "followTheStepsBelowToFinishLoggingIn": { "message": "Follow the steps below to finish logging in." }, + "followTheStepsBelowToFinishLoggingInWithSecurityKey": { + "message": "Follow the steps below to finish logging in with your security key." + }, "launchDuo": { "message": "تشغيل دوو في المتصفح" }, @@ -3436,6 +3549,17 @@ "ssoError": { "message": "لم يُعثر على أي منفذ مجاني لتسجيل الدخول." }, + "securePasswordGenerated": { + "message": "Secure password generated! Don't forget to also update your password on the website." + }, + "useGeneratorHelpTextPartOne": { + "message": "Use the generator", + "description": "This will be used as part of a larger sentence, broken up to include the generator icon. The full sentence will read 'Use the generator [GENERATOR_ICON] to create a strong unique password'" + }, + "useGeneratorHelpTextPartTwo": { + "message": "to create a strong unique password", + "description": "This will be used as part of a larger sentence, broken up to include the generator icon. The full sentence will read 'Use the generator [GENERATOR_ICON] to create a strong unique password'" + }, "biometricsStatusHelptextUnlockNeeded": { "message": "Biometric unlock is unavailable because PIN or password unlock is required first." }, @@ -3514,6 +3638,27 @@ "setupTwoStepLogin": { "message": "إعداد تسجيل الدخول بخطوتين" }, + "itemDetails": { + "message": "Item details" + }, + "itemName": { + "message": "Item name" + }, + "loginCredentials": { + "message": "Login credentials" + }, + "additionalOptions": { + "message": "Additional options" + }, + "itemHistory": { + "message": "Item history" + }, + "lastEdited": { + "message": "Last edited" + }, + "upload": { + "message": "Upload" + }, "newDeviceVerificationNoticeContentPage1": { "message": "سيقوم Bitwarden بإرسال رمز إلى البريد الإلكتروني الخاص بحسابك للتحقق من تسجيلات الدخول من الأجهزة الجديدة ابتداءً من فبراير 2025." }, @@ -3567,5 +3712,35 @@ }, "move": { "message": "Move" + }, + "newLoginNudgeTitle": { + "message": "Save time with autofill" + }, + "newLoginNudgeBody": { + "message": "Include a Website so this login appears as an autofill suggestion." + }, + "newCardNudgeTitle": { + "message": "Seamless online checkout" + }, + "newCardNudgeBody": { + "message": "With cards, easily autofill payment forms securely and accurately." + }, + "newIdentityNudgeTitle": { + "message": "Simplify creating accounts" + }, + "newIdentityNudgeBody": { + "message": "With identities, quickly autofill long registration or contact forms." + }, + "newNoteNudgeTitle": { + "message": "Keep your sensitive data safe" + }, + "newNoteNudgeBody": { + "message": "With notes, securely store sensitive data like banking or insurance details." + }, + "newSshNudgeTitle": { + "message": "Developer-friendly SSH access" + }, + "newSshNudgeBody": { + "message": "Store your keys and connect with the SSH agent for fast, encrypted authentication." } } diff --git a/apps/desktop/src/locales/az/messages.json b/apps/desktop/src/locales/az/messages.json index abfbb6b108a..b3c61e0af43 100644 --- a/apps/desktop/src/locales/az/messages.json +++ b/apps/desktop/src/locales/az/messages.json @@ -393,6 +393,64 @@ "authenticatorKeyTotp": { "message": "Kimlik doğrulayıcı açarı (TOTP)" }, + "authenticatorKey": { + "message": "Kimlik doğrulayıcı açarı" + }, + "autofillOptions": { + "message": "Avto-doldurma seçimləri" + }, + "websiteUri": { + "message": "Veb sayt (URI)" + }, + "websiteUriCount": { + "message": "Veb sayt (URI) $COUNT$", + "description": "Label for an input field that contains a website URI. The input field is part of a list of fields, and the count indicates the position of the field in the list.", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + } + } + }, + "websiteAdded": { + "message": "Veb sayt əlavə edildi" + }, + "addWebsite": { + "message": "Veb sayt əlavə et" + }, + "deleteWebsite": { + "message": "Veb saytı sil" + }, + "owner": { + "message": "Sahibi" + }, + "addField": { + "message": "Xana əlavə et" + }, + "fieldType": { + "message": "Xana növü" + }, + "fieldLabel": { + "message": "Xana etiketi" + }, + "add": { + "message": "Əlavə et" + }, + "textHelpText": { + "message": "Təhlükəsizlik sualları kimi datalar üçün mətn xanalarını istifadə edin" + }, + "hiddenHelpText": { + "message": "Parol kimi həssas datalar üçün gizli xanaları istifadə edin" + }, + "checkBoxHelpText": { + "message": "\"E-poçtu xatırla\" kimi formun təsdiq qutusunu avto-doldurmaq istəyirsinizsə təsdiq qutularını istifadə edin" + }, + "linkedHelpText": { + "message": "Müəyyən bir veb sayt üçün avto-doldurma problemləri ilə üzləşdikdə əlaqələndirilmiş xana istifadə edin." + }, + "linkedLabelHelpText": { + "message": "Xana üçün bunları daxil edin: kimlik, ad, aria-label və ya placeholder." + }, "folder": { "message": "Qovluq" }, @@ -418,6 +476,9 @@ "message": "Əlaqə yaradıldı", "description": "This describes a field that is 'linked' (related) to another field." }, + "cfTypeCheckbox": { + "message": "Təsdiq qutusu" + }, "linkedValue": { "message": "Əlaqəli dəyər", "description": "This describes a value that is 'linked' (related) to another value." @@ -1915,6 +1976,43 @@ } } }, + "cardDetails": { + "message": "Kart detalları" + }, + "cardBrandDetails": { + "message": "$BRAND$ detalları", + "placeholders": { + "brand": { + "content": "$1", + "example": "Visa" + } + } + }, + "learnMoreAboutAuthenticators": { + "message": "Kimlik doğrulayıcılar haqqında daha ətraflı" + }, + "copyTOTP": { + "message": "Kimlik doğrulayıcı açarını kopyala (TOTP)" + }, + "totpHelperTitle": { + "message": "2 addımlı doğrulamanı problemsiz edin" + }, + "totpHelper": { + "message": "Bitwarden, 2 addımlı doğrulama kodlarını saxlaya və doldura bilər. Açarı kopyalayıb bu xanaya yapışdırın." + }, + "totpHelperWithCapture": { + "message": "Bitwarden, 2 addımlı doğrulama kodlarını saxlaya və doldura bilər. Bu veb saytdakı kimlik doğrulayıcı QR kodunun ekran şəklini çəkmək üçün kamera ikonunu seçin, ya da açarı kopyalayıb bu xanaya yapışdırın." + }, + "premium": { + "message": "Premium", + "description": "Premium membership" + }, + "cardExpiredTitle": { + "message": "Kartın müddəti bitib" + }, + "cardExpiredMessage": { + "message": "Yeniləmisinizsə, kart məlumatlarınızı güncəlləyin" + }, "verificationRequired": { "message": "Doğrulama tələb olunur", "description": "Default title for the user verification dialog." @@ -2084,6 +2182,15 @@ "personalOwnershipPolicyInEffectImports": { "message": "Bir təşkilat siyasəti, elementlərin fərdi seyfinizə köçürülməsini əngəllədi." }, + "personalDetails": { + "message": "Şəxsi detallar" + }, + "identification": { + "message": "İdentifikasiya" + }, + "contactInfo": { + "message": "Əlaqə məlumatları" + }, "allSends": { "message": "Bütün \"Send\"lər", "description": "'Sends' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." @@ -2419,7 +2526,7 @@ "message": "Təşkilatı tərk etdiniz." }, "ssoKeyConnectorError": { - "message": "Açar bağlayıcı xətası: Açar Bağlayıcının mövcud olduğuna və düzgün işlədiyinə əmin olun." + "message": "Key connector xətası: \"Key connector\"un mövcud olduğuna və düzgün işlədiyinə əmin olun." }, "lockAllVaults": { "message": "Bütün seyfləri kilidlə" @@ -2518,6 +2625,9 @@ "generateEmail": { "message": "E-poçt yarat" }, + "usernameGenerator": { + "message": "İstifadəçi adı yaradıcı" + }, "spinboxBoundariesHint": { "message": "Dəyər, $MIN$-$MAX$ arasında olmalıdır.", "description": "Explains spin box minimum and maximum values to the user", @@ -3212,6 +3322,9 @@ "followTheStepsBelowToFinishLoggingIn": { "message": "Giriş prosesini tamamlamaq üçün aşağıdakı addımları izləyin." }, + "followTheStepsBelowToFinishLoggingInWithSecurityKey": { + "message": "Güvənlik açarınızla girişi tamamlamaq üçün aşağıdakı addımları izləyin." + }, "launchDuo": { "message": "Duo-nu brauzerdə başlat" }, @@ -3436,6 +3549,17 @@ "ssoError": { "message": "SSO giriş üçün açıq port tapıla bilmədi." }, + "securePasswordGenerated": { + "message": "Güvənli parol yaradıldı! Həmçinin veb saytdakı parolunuzu güncəlləməyi unutmayın." + }, + "useGeneratorHelpTextPartOne": { + "message": "Yaradıcı istifadə et", + "description": "This will be used as part of a larger sentence, broken up to include the generator icon. The full sentence will read 'Use the generator [GENERATOR_ICON] to create a strong unique password'" + }, + "useGeneratorHelpTextPartTwo": { + "message": "Daha unikal parollar yarat", + "description": "This will be used as part of a larger sentence, broken up to include the generator icon. The full sentence will read 'Use the generator [GENERATOR_ICON] to create a strong unique password'" + }, "biometricsStatusHelptextUnlockNeeded": { "message": "Əvvəlcə PIN və ya parol ilə kilid açma tələb olunduğu üçün biometrik ilə kilid açma əlçatmazdır." }, @@ -3514,6 +3638,27 @@ "setupTwoStepLogin": { "message": "İki addımlı girişi qur" }, + "itemDetails": { + "message": "Element detalları" + }, + "itemName": { + "message": "Element adı" + }, + "loginCredentials": { + "message": "Giriş məlumatları" + }, + "additionalOptions": { + "message": "Əlavə seçimlər" + }, + "itemHistory": { + "message": "Element tarixçəsi" + }, + "lastEdited": { + "message": "Son düzəliş" + }, + "upload": { + "message": "Yüklə" + }, "newDeviceVerificationNoticeContentPage1": { "message": "Bitwarden, 2025-ci ilin Fevral ayından etibarən yeni cihazlardan gələn girişləri doğrulamaq üçün hesabınızın e-poçtuna bir kod göndərəcək." }, @@ -3567,5 +3712,35 @@ }, "move": { "message": "Daşı" + }, + "newLoginNudgeTitle": { + "message": "Save time with autofill" + }, + "newLoginNudgeBody": { + "message": "Include a Website so this login appears as an autofill suggestion." + }, + "newCardNudgeTitle": { + "message": "Seamless online checkout" + }, + "newCardNudgeBody": { + "message": "With cards, easily autofill payment forms securely and accurately." + }, + "newIdentityNudgeTitle": { + "message": "Simplify creating accounts" + }, + "newIdentityNudgeBody": { + "message": "With identities, quickly autofill long registration or contact forms." + }, + "newNoteNudgeTitle": { + "message": "Keep your sensitive data safe" + }, + "newNoteNudgeBody": { + "message": "With notes, securely store sensitive data like banking or insurance details." + }, + "newSshNudgeTitle": { + "message": "Developer-friendly SSH access" + }, + "newSshNudgeBody": { + "message": "Store your keys and connect with the SSH agent for fast, encrypted authentication." } } diff --git a/apps/desktop/src/locales/be/messages.json b/apps/desktop/src/locales/be/messages.json index 287fbb6c4c3..c01caa5c0ba 100644 --- a/apps/desktop/src/locales/be/messages.json +++ b/apps/desktop/src/locales/be/messages.json @@ -393,6 +393,64 @@ "authenticatorKeyTotp": { "message": "Ключ аўтэнтыфікацыі (TOTP)" }, + "authenticatorKey": { + "message": "Authenticator key" + }, + "autofillOptions": { + "message": "Autofill options" + }, + "websiteUri": { + "message": "Website (URI)" + }, + "websiteUriCount": { + "message": "Website (URI) $COUNT$", + "description": "Label for an input field that contains a website URI. The input field is part of a list of fields, and the count indicates the position of the field in the list.", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + } + } + }, + "websiteAdded": { + "message": "Website added" + }, + "addWebsite": { + "message": "Add website" + }, + "deleteWebsite": { + "message": "Delete website" + }, + "owner": { + "message": "Owner" + }, + "addField": { + "message": "Add field" + }, + "fieldType": { + "message": "Field type" + }, + "fieldLabel": { + "message": "Field label" + }, + "add": { + "message": "Add" + }, + "textHelpText": { + "message": "Use text fields for data like security questions" + }, + "hiddenHelpText": { + "message": "Use hidden fields for sensitive data like a password" + }, + "checkBoxHelpText": { + "message": "Use checkboxes if you'd like to autofill a form's checkbox, like a remember email" + }, + "linkedHelpText": { + "message": "Use a linked field when you are experiencing autofill issues for a specific website." + }, + "linkedLabelHelpText": { + "message": "Enter the the field's html id, name, aria-label, or placeholder." + }, "folder": { "message": "Папка" }, @@ -418,6 +476,9 @@ "message": "Звязана", "description": "This describes a field that is 'linked' (related) to another field." }, + "cfTypeCheckbox": { + "message": "Checkbox" + }, "linkedValue": { "message": "Звязанае значэнне", "description": "This describes a value that is 'linked' (related) to another value." @@ -1915,6 +1976,43 @@ } } }, + "cardDetails": { + "message": "Card details" + }, + "cardBrandDetails": { + "message": "$BRAND$ details", + "placeholders": { + "brand": { + "content": "$1", + "example": "Visa" + } + } + }, + "learnMoreAboutAuthenticators": { + "message": "Learn more about authenticators" + }, + "copyTOTP": { + "message": "Copy Authenticator key (TOTP)" + }, + "totpHelperTitle": { + "message": "Make 2-step verification seamless" + }, + "totpHelper": { + "message": "Bitwarden can store and fill 2-step verification codes. Copy and paste the key into this field." + }, + "totpHelperWithCapture": { + "message": "Bitwarden can store and fill 2-step verification codes. Select the camera icon to take a screenshot of this website's authenticator QR code, or copy and paste the key into this field." + }, + "premium": { + "message": "Premium", + "description": "Premium membership" + }, + "cardExpiredTitle": { + "message": "Expired card" + }, + "cardExpiredMessage": { + "message": "If you've renewed it, update the card's information" + }, "verificationRequired": { "message": "Verification required", "description": "Default title for the user verification dialog." @@ -2084,6 +2182,15 @@ "personalOwnershipPolicyInEffectImports": { "message": "An organization policy has blocked importing items into your individual vault." }, + "personalDetails": { + "message": "Personal details" + }, + "identification": { + "message": "Identification" + }, + "contactInfo": { + "message": "Contact information" + }, "allSends": { "message": "Усе Send'ы", "description": "'Sends' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." @@ -2518,6 +2625,9 @@ "generateEmail": { "message": "Generate email" }, + "usernameGenerator": { + "message": "Username generator" + }, "spinboxBoundariesHint": { "message": "Value must be between $MIN$ and $MAX$.", "description": "Explains spin box minimum and maximum values to the user", @@ -3212,6 +3322,9 @@ "followTheStepsBelowToFinishLoggingIn": { "message": "Follow the steps below to finish logging in." }, + "followTheStepsBelowToFinishLoggingInWithSecurityKey": { + "message": "Follow the steps below to finish logging in with your security key." + }, "launchDuo": { "message": "Launch Duo in Browser" }, @@ -3436,6 +3549,17 @@ "ssoError": { "message": "No free ports could be found for the sso login." }, + "securePasswordGenerated": { + "message": "Secure password generated! Don't forget to also update your password on the website." + }, + "useGeneratorHelpTextPartOne": { + "message": "Use the generator", + "description": "This will be used as part of a larger sentence, broken up to include the generator icon. The full sentence will read 'Use the generator [GENERATOR_ICON] to create a strong unique password'" + }, + "useGeneratorHelpTextPartTwo": { + "message": "to create a strong unique password", + "description": "This will be used as part of a larger sentence, broken up to include the generator icon. The full sentence will read 'Use the generator [GENERATOR_ICON] to create a strong unique password'" + }, "biometricsStatusHelptextUnlockNeeded": { "message": "Biometric unlock is unavailable because PIN or password unlock is required first." }, @@ -3514,6 +3638,27 @@ "setupTwoStepLogin": { "message": "Set up two-step login" }, + "itemDetails": { + "message": "Item details" + }, + "itemName": { + "message": "Item name" + }, + "loginCredentials": { + "message": "Login credentials" + }, + "additionalOptions": { + "message": "Additional options" + }, + "itemHistory": { + "message": "Item history" + }, + "lastEdited": { + "message": "Last edited" + }, + "upload": { + "message": "Upload" + }, "newDeviceVerificationNoticeContentPage1": { "message": "Bitwarden will send a code to your account email to verify logins from new devices starting in February 2025." }, @@ -3567,5 +3712,35 @@ }, "move": { "message": "Move" + }, + "newLoginNudgeTitle": { + "message": "Save time with autofill" + }, + "newLoginNudgeBody": { + "message": "Include a Website so this login appears as an autofill suggestion." + }, + "newCardNudgeTitle": { + "message": "Seamless online checkout" + }, + "newCardNudgeBody": { + "message": "With cards, easily autofill payment forms securely and accurately." + }, + "newIdentityNudgeTitle": { + "message": "Simplify creating accounts" + }, + "newIdentityNudgeBody": { + "message": "With identities, quickly autofill long registration or contact forms." + }, + "newNoteNudgeTitle": { + "message": "Keep your sensitive data safe" + }, + "newNoteNudgeBody": { + "message": "With notes, securely store sensitive data like banking or insurance details." + }, + "newSshNudgeTitle": { + "message": "Developer-friendly SSH access" + }, + "newSshNudgeBody": { + "message": "Store your keys and connect with the SSH agent for fast, encrypted authentication." } } diff --git a/apps/desktop/src/locales/bg/messages.json b/apps/desktop/src/locales/bg/messages.json index 0e47828c86c..1db8f182818 100644 --- a/apps/desktop/src/locales/bg/messages.json +++ b/apps/desktop/src/locales/bg/messages.json @@ -393,6 +393,64 @@ "authenticatorKeyTotp": { "message": "Удостоверителен ключ (TOTP)" }, + "authenticatorKey": { + "message": "Ключ за удостоверяване" + }, + "autofillOptions": { + "message": "Опции за автоматично попълване" + }, + "websiteUri": { + "message": "Уеб сайт (адрес)" + }, + "websiteUriCount": { + "message": "Уеб сайт (адрес) $COUNT$", + "description": "Label for an input field that contains a website URI. The input field is part of a list of fields, and the count indicates the position of the field in the list.", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + } + } + }, + "websiteAdded": { + "message": "Уеб сайтът е добавен" + }, + "addWebsite": { + "message": "Добавяне на уеб сайт" + }, + "deleteWebsite": { + "message": "Изтриване на уеб сайт" + }, + "owner": { + "message": "Собственик" + }, + "addField": { + "message": "Добавяне на поле" + }, + "fieldType": { + "message": "Тип на полето" + }, + "fieldLabel": { + "message": "Етикет на полето" + }, + "add": { + "message": "Добавяне" + }, + "textHelpText": { + "message": "Използвайте текстови полета за обикновени данни, например въпроси за сигурност" + }, + "hiddenHelpText": { + "message": "Използвайте скрити полета за чувствителни данни, например пароли" + }, + "checkBoxHelpText": { + "message": "Използвайте полета за отметки, ако искате да попълвате автоматично такива полета във формуляри, например такова за запомняне на е-пощата" + }, + "linkedHelpText": { + "message": "Използвайте свързано поле, когато имате проблеми с автоматичното попълване на конкретен уеб сайт." + }, + "linkedLabelHelpText": { + "message": "Въведете подробности за полето от атрибутите му в HTML – id, name, aria-label или placeholder." + }, "folder": { "message": "Папка" }, @@ -418,6 +476,9 @@ "message": "Свързано", "description": "This describes a field that is 'linked' (related) to another field." }, + "cfTypeCheckbox": { + "message": "Поле за отметка" + }, "linkedValue": { "message": "Свързана стойност", "description": "This describes a value that is 'linked' (related) to another value." @@ -1915,6 +1976,43 @@ } } }, + "cardDetails": { + "message": "Данни за картата" + }, + "cardBrandDetails": { + "message": "Подробности за $BRAND$", + "placeholders": { + "brand": { + "content": "$1", + "example": "Visa" + } + } + }, + "learnMoreAboutAuthenticators": { + "message": "Научете повече за средствата за удостоверяване" + }, + "copyTOTP": { + "message": "Копиране на удостоверителния ключ (TOTP)" + }, + "totpHelperTitle": { + "message": "Направете 2-стъпковото удостоверяване безпроблемно и незабележимо" + }, + "totpHelper": { + "message": "Битуорден може да съхранява и попълва кодовете за 2-стъпково потвърждаване. Копирайте ключа в това поле." + }, + "totpHelperWithCapture": { + "message": "Битуорден може да съхранява и попълва кодовете за 2-стъпково потвърждаване. Изберете иконката с камера, за да направите екранна снимка на QR-кода от този уеб сайт или копирайте ключа в това поле." + }, + "premium": { + "message": "Премиум", + "description": "Premium membership" + }, + "cardExpiredTitle": { + "message": "Изтекла карта" + }, + "cardExpiredMessage": { + "message": "Ако сте я подновили, актуализирайте информацията за картата" + }, "verificationRequired": { "message": "Изисква се потвърждение", "description": "Default title for the user verification dialog." @@ -2084,6 +2182,15 @@ "personalOwnershipPolicyInEffectImports": { "message": "Политика на организацията забранява да внасяте елементи в личния си трезор." }, + "personalDetails": { + "message": "Лични данни" + }, + "identification": { + "message": "Идентификация" + }, + "contactInfo": { + "message": "Информация за контакт" + }, "allSends": { "message": "Всички изпращания", "description": "'Sends' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." @@ -2518,6 +2625,9 @@ "generateEmail": { "message": "Генериране на електронна поща" }, + "usernameGenerator": { + "message": "Генератор на потребителски имена" + }, "spinboxBoundariesHint": { "message": "Стойността трябва да бъде между $MIN$ и $MAX$.", "description": "Explains spin box minimum and maximum values to the user", @@ -3212,6 +3322,9 @@ "followTheStepsBelowToFinishLoggingIn": { "message": "Следвайте стъпките по-долу, за да завършите вписването." }, + "followTheStepsBelowToFinishLoggingInWithSecurityKey": { + "message": "Следвайте стъпките по-долу, за да завършите вписването си чрез устройството за удостоверяване." + }, "launchDuo": { "message": "Стартирайте Duo в браузъра" }, @@ -3436,6 +3549,17 @@ "ssoError": { "message": "Не могат да бъдат открити свободни портове за еднократната идентификация." }, + "securePasswordGenerated": { + "message": "Създадена е сигурна парола! Не забравяйте и да промените паролата си в уеб сайта." + }, + "useGeneratorHelpTextPartOne": { + "message": "Използвайте генератора", + "description": "This will be used as part of a larger sentence, broken up to include the generator icon. The full sentence will read 'Use the generator [GENERATOR_ICON] to create a strong unique password'" + }, + "useGeneratorHelpTextPartTwo": { + "message": "за да създадете сигурна и уникална парола", + "description": "This will be used as part of a larger sentence, broken up to include the generator icon. The full sentence will read 'Use the generator [GENERATOR_ICON] to create a strong unique password'" + }, "biometricsStatusHelptextUnlockNeeded": { "message": "Отключването с биометрични данни не е налично, тъй като първо се изисква отключване чрез ПИН или парола." }, @@ -3514,6 +3638,27 @@ "setupTwoStepLogin": { "message": "Настройте двустепенно удостоверяване" }, + "itemDetails": { + "message": "Подробности за елемента" + }, + "itemName": { + "message": "Име на елемента" + }, + "loginCredentials": { + "message": "Данни за вписване" + }, + "additionalOptions": { + "message": "Допълнителни настройки" + }, + "itemHistory": { + "message": "История на елемента" + }, + "lastEdited": { + "message": "Последна промяна" + }, + "upload": { + "message": "Качване" + }, "newDeviceVerificationNoticeContentPage1": { "message": "Битуорден ще изпрати код до е-пощата Ви, за потвърждаване на вписването от нови устройства. Това ще започне от февруари 2025." }, @@ -3567,5 +3712,35 @@ }, "move": { "message": "Преместване" + }, + "newLoginNudgeTitle": { + "message": "Save time with autofill" + }, + "newLoginNudgeBody": { + "message": "Include a Website so this login appears as an autofill suggestion." + }, + "newCardNudgeTitle": { + "message": "Seamless online checkout" + }, + "newCardNudgeBody": { + "message": "With cards, easily autofill payment forms securely and accurately." + }, + "newIdentityNudgeTitle": { + "message": "Simplify creating accounts" + }, + "newIdentityNudgeBody": { + "message": "With identities, quickly autofill long registration or contact forms." + }, + "newNoteNudgeTitle": { + "message": "Keep your sensitive data safe" + }, + "newNoteNudgeBody": { + "message": "With notes, securely store sensitive data like banking or insurance details." + }, + "newSshNudgeTitle": { + "message": "Developer-friendly SSH access" + }, + "newSshNudgeBody": { + "message": "Store your keys and connect with the SSH agent for fast, encrypted authentication." } } diff --git a/apps/desktop/src/locales/bn/messages.json b/apps/desktop/src/locales/bn/messages.json index 7d29d4ee663..f41dcb12057 100644 --- a/apps/desktop/src/locales/bn/messages.json +++ b/apps/desktop/src/locales/bn/messages.json @@ -393,6 +393,64 @@ "authenticatorKeyTotp": { "message": "প্রমাণীকরণকারী কী (TOTP)" }, + "authenticatorKey": { + "message": "Authenticator key" + }, + "autofillOptions": { + "message": "Autofill options" + }, + "websiteUri": { + "message": "Website (URI)" + }, + "websiteUriCount": { + "message": "Website (URI) $COUNT$", + "description": "Label for an input field that contains a website URI. The input field is part of a list of fields, and the count indicates the position of the field in the list.", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + } + } + }, + "websiteAdded": { + "message": "Website added" + }, + "addWebsite": { + "message": "Add website" + }, + "deleteWebsite": { + "message": "Delete website" + }, + "owner": { + "message": "Owner" + }, + "addField": { + "message": "Add field" + }, + "fieldType": { + "message": "Field type" + }, + "fieldLabel": { + "message": "Field label" + }, + "add": { + "message": "Add" + }, + "textHelpText": { + "message": "Use text fields for data like security questions" + }, + "hiddenHelpText": { + "message": "Use hidden fields for sensitive data like a password" + }, + "checkBoxHelpText": { + "message": "Use checkboxes if you'd like to autofill a form's checkbox, like a remember email" + }, + "linkedHelpText": { + "message": "Use a linked field when you are experiencing autofill issues for a specific website." + }, + "linkedLabelHelpText": { + "message": "Enter the the field's html id, name, aria-label, or placeholder." + }, "folder": { "message": "ফোল্ডার" }, @@ -418,6 +476,9 @@ "message": "Linked", "description": "This describes a field that is 'linked' (related) to another field." }, + "cfTypeCheckbox": { + "message": "Checkbox" + }, "linkedValue": { "message": "Linked value", "description": "This describes a value that is 'linked' (related) to another value." @@ -1915,6 +1976,43 @@ } } }, + "cardDetails": { + "message": "Card details" + }, + "cardBrandDetails": { + "message": "$BRAND$ details", + "placeholders": { + "brand": { + "content": "$1", + "example": "Visa" + } + } + }, + "learnMoreAboutAuthenticators": { + "message": "Learn more about authenticators" + }, + "copyTOTP": { + "message": "Copy Authenticator key (TOTP)" + }, + "totpHelperTitle": { + "message": "Make 2-step verification seamless" + }, + "totpHelper": { + "message": "Bitwarden can store and fill 2-step verification codes. Copy and paste the key into this field." + }, + "totpHelperWithCapture": { + "message": "Bitwarden can store and fill 2-step verification codes. Select the camera icon to take a screenshot of this website's authenticator QR code, or copy and paste the key into this field." + }, + "premium": { + "message": "Premium", + "description": "Premium membership" + }, + "cardExpiredTitle": { + "message": "Expired card" + }, + "cardExpiredMessage": { + "message": "If you've renewed it, update the card's information" + }, "verificationRequired": { "message": "Verification required", "description": "Default title for the user verification dialog." @@ -2084,6 +2182,15 @@ "personalOwnershipPolicyInEffectImports": { "message": "An organization policy has blocked importing items into your individual vault." }, + "personalDetails": { + "message": "Personal details" + }, + "identification": { + "message": "Identification" + }, + "contactInfo": { + "message": "Contact information" + }, "allSends": { "message": "All Sends", "description": "'Sends' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." @@ -2518,6 +2625,9 @@ "generateEmail": { "message": "Generate email" }, + "usernameGenerator": { + "message": "Username generator" + }, "spinboxBoundariesHint": { "message": "Value must be between $MIN$ and $MAX$.", "description": "Explains spin box minimum and maximum values to the user", @@ -3212,6 +3322,9 @@ "followTheStepsBelowToFinishLoggingIn": { "message": "Follow the steps below to finish logging in." }, + "followTheStepsBelowToFinishLoggingInWithSecurityKey": { + "message": "Follow the steps below to finish logging in with your security key." + }, "launchDuo": { "message": "Launch Duo in Browser" }, @@ -3436,6 +3549,17 @@ "ssoError": { "message": "No free ports could be found for the sso login." }, + "securePasswordGenerated": { + "message": "Secure password generated! Don't forget to also update your password on the website." + }, + "useGeneratorHelpTextPartOne": { + "message": "Use the generator", + "description": "This will be used as part of a larger sentence, broken up to include the generator icon. The full sentence will read 'Use the generator [GENERATOR_ICON] to create a strong unique password'" + }, + "useGeneratorHelpTextPartTwo": { + "message": "to create a strong unique password", + "description": "This will be used as part of a larger sentence, broken up to include the generator icon. The full sentence will read 'Use the generator [GENERATOR_ICON] to create a strong unique password'" + }, "biometricsStatusHelptextUnlockNeeded": { "message": "Biometric unlock is unavailable because PIN or password unlock is required first." }, @@ -3514,6 +3638,27 @@ "setupTwoStepLogin": { "message": "Set up two-step login" }, + "itemDetails": { + "message": "Item details" + }, + "itemName": { + "message": "Item name" + }, + "loginCredentials": { + "message": "Login credentials" + }, + "additionalOptions": { + "message": "Additional options" + }, + "itemHistory": { + "message": "Item history" + }, + "lastEdited": { + "message": "Last edited" + }, + "upload": { + "message": "Upload" + }, "newDeviceVerificationNoticeContentPage1": { "message": "Bitwarden will send a code to your account email to verify logins from new devices starting in February 2025." }, @@ -3567,5 +3712,35 @@ }, "move": { "message": "Move" + }, + "newLoginNudgeTitle": { + "message": "Save time with autofill" + }, + "newLoginNudgeBody": { + "message": "Include a Website so this login appears as an autofill suggestion." + }, + "newCardNudgeTitle": { + "message": "Seamless online checkout" + }, + "newCardNudgeBody": { + "message": "With cards, easily autofill payment forms securely and accurately." + }, + "newIdentityNudgeTitle": { + "message": "Simplify creating accounts" + }, + "newIdentityNudgeBody": { + "message": "With identities, quickly autofill long registration or contact forms." + }, + "newNoteNudgeTitle": { + "message": "Keep your sensitive data safe" + }, + "newNoteNudgeBody": { + "message": "With notes, securely store sensitive data like banking or insurance details." + }, + "newSshNudgeTitle": { + "message": "Developer-friendly SSH access" + }, + "newSshNudgeBody": { + "message": "Store your keys and connect with the SSH agent for fast, encrypted authentication." } } diff --git a/apps/desktop/src/locales/bs/messages.json b/apps/desktop/src/locales/bs/messages.json index 66f8d3b72ac..3188a1fa889 100644 --- a/apps/desktop/src/locales/bs/messages.json +++ b/apps/desktop/src/locales/bs/messages.json @@ -393,6 +393,64 @@ "authenticatorKeyTotp": { "message": "Ključ autentifikatora (TOTP)" }, + "authenticatorKey": { + "message": "Authenticator key" + }, + "autofillOptions": { + "message": "Autofill options" + }, + "websiteUri": { + "message": "Website (URI)" + }, + "websiteUriCount": { + "message": "Website (URI) $COUNT$", + "description": "Label for an input field that contains a website URI. The input field is part of a list of fields, and the count indicates the position of the field in the list.", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + } + } + }, + "websiteAdded": { + "message": "Website added" + }, + "addWebsite": { + "message": "Add website" + }, + "deleteWebsite": { + "message": "Delete website" + }, + "owner": { + "message": "Owner" + }, + "addField": { + "message": "Add field" + }, + "fieldType": { + "message": "Field type" + }, + "fieldLabel": { + "message": "Field label" + }, + "add": { + "message": "Add" + }, + "textHelpText": { + "message": "Use text fields for data like security questions" + }, + "hiddenHelpText": { + "message": "Use hidden fields for sensitive data like a password" + }, + "checkBoxHelpText": { + "message": "Use checkboxes if you'd like to autofill a form's checkbox, like a remember email" + }, + "linkedHelpText": { + "message": "Use a linked field when you are experiencing autofill issues for a specific website." + }, + "linkedLabelHelpText": { + "message": "Enter the the field's html id, name, aria-label, or placeholder." + }, "folder": { "message": "Folder" }, @@ -418,6 +476,9 @@ "message": "Povezano sa", "description": "This describes a field that is 'linked' (related) to another field." }, + "cfTypeCheckbox": { + "message": "Checkbox" + }, "linkedValue": { "message": "Povezana vrijednost", "description": "This describes a value that is 'linked' (related) to another value." @@ -1915,6 +1976,43 @@ } } }, + "cardDetails": { + "message": "Card details" + }, + "cardBrandDetails": { + "message": "$BRAND$ details", + "placeholders": { + "brand": { + "content": "$1", + "example": "Visa" + } + } + }, + "learnMoreAboutAuthenticators": { + "message": "Learn more about authenticators" + }, + "copyTOTP": { + "message": "Copy Authenticator key (TOTP)" + }, + "totpHelperTitle": { + "message": "Make 2-step verification seamless" + }, + "totpHelper": { + "message": "Bitwarden can store and fill 2-step verification codes. Copy and paste the key into this field." + }, + "totpHelperWithCapture": { + "message": "Bitwarden can store and fill 2-step verification codes. Select the camera icon to take a screenshot of this website's authenticator QR code, or copy and paste the key into this field." + }, + "premium": { + "message": "Premium", + "description": "Premium membership" + }, + "cardExpiredTitle": { + "message": "Expired card" + }, + "cardExpiredMessage": { + "message": "If you've renewed it, update the card's information" + }, "verificationRequired": { "message": "Verification required", "description": "Default title for the user verification dialog." @@ -2084,6 +2182,15 @@ "personalOwnershipPolicyInEffectImports": { "message": "An organization policy has blocked importing items into your individual vault." }, + "personalDetails": { + "message": "Personal details" + }, + "identification": { + "message": "Identification" + }, + "contactInfo": { + "message": "Contact information" + }, "allSends": { "message": "Svi Send-ovi", "description": "'Sends' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." @@ -2518,6 +2625,9 @@ "generateEmail": { "message": "Generate email" }, + "usernameGenerator": { + "message": "Username generator" + }, "spinboxBoundariesHint": { "message": "Value must be between $MIN$ and $MAX$.", "description": "Explains spin box minimum and maximum values to the user", @@ -3212,6 +3322,9 @@ "followTheStepsBelowToFinishLoggingIn": { "message": "Follow the steps below to finish logging in." }, + "followTheStepsBelowToFinishLoggingInWithSecurityKey": { + "message": "Follow the steps below to finish logging in with your security key." + }, "launchDuo": { "message": "Launch Duo in Browser" }, @@ -3436,6 +3549,17 @@ "ssoError": { "message": "No free ports could be found for the sso login." }, + "securePasswordGenerated": { + "message": "Secure password generated! Don't forget to also update your password on the website." + }, + "useGeneratorHelpTextPartOne": { + "message": "Use the generator", + "description": "This will be used as part of a larger sentence, broken up to include the generator icon. The full sentence will read 'Use the generator [GENERATOR_ICON] to create a strong unique password'" + }, + "useGeneratorHelpTextPartTwo": { + "message": "to create a strong unique password", + "description": "This will be used as part of a larger sentence, broken up to include the generator icon. The full sentence will read 'Use the generator [GENERATOR_ICON] to create a strong unique password'" + }, "biometricsStatusHelptextUnlockNeeded": { "message": "Biometric unlock is unavailable because PIN or password unlock is required first." }, @@ -3514,6 +3638,27 @@ "setupTwoStepLogin": { "message": "Set up two-step login" }, + "itemDetails": { + "message": "Item details" + }, + "itemName": { + "message": "Item name" + }, + "loginCredentials": { + "message": "Login credentials" + }, + "additionalOptions": { + "message": "Additional options" + }, + "itemHistory": { + "message": "Item history" + }, + "lastEdited": { + "message": "Last edited" + }, + "upload": { + "message": "Upload" + }, "newDeviceVerificationNoticeContentPage1": { "message": "Bitwarden will send a code to your account email to verify logins from new devices starting in February 2025." }, @@ -3567,5 +3712,35 @@ }, "move": { "message": "Move" + }, + "newLoginNudgeTitle": { + "message": "Save time with autofill" + }, + "newLoginNudgeBody": { + "message": "Include a Website so this login appears as an autofill suggestion." + }, + "newCardNudgeTitle": { + "message": "Seamless online checkout" + }, + "newCardNudgeBody": { + "message": "With cards, easily autofill payment forms securely and accurately." + }, + "newIdentityNudgeTitle": { + "message": "Simplify creating accounts" + }, + "newIdentityNudgeBody": { + "message": "With identities, quickly autofill long registration or contact forms." + }, + "newNoteNudgeTitle": { + "message": "Keep your sensitive data safe" + }, + "newNoteNudgeBody": { + "message": "With notes, securely store sensitive data like banking or insurance details." + }, + "newSshNudgeTitle": { + "message": "Developer-friendly SSH access" + }, + "newSshNudgeBody": { + "message": "Store your keys and connect with the SSH agent for fast, encrypted authentication." } } diff --git a/apps/desktop/src/locales/ca/messages.json b/apps/desktop/src/locales/ca/messages.json index f3fcc17fed9..f989d530b2d 100644 --- a/apps/desktop/src/locales/ca/messages.json +++ b/apps/desktop/src/locales/ca/messages.json @@ -393,6 +393,64 @@ "authenticatorKeyTotp": { "message": "Clau d'autenticació (TOTP)" }, + "authenticatorKey": { + "message": "Authenticator key" + }, + "autofillOptions": { + "message": "Autofill options" + }, + "websiteUri": { + "message": "Website (URI)" + }, + "websiteUriCount": { + "message": "Website (URI) $COUNT$", + "description": "Label for an input field that contains a website URI. The input field is part of a list of fields, and the count indicates the position of the field in the list.", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + } + } + }, + "websiteAdded": { + "message": "Website added" + }, + "addWebsite": { + "message": "Add website" + }, + "deleteWebsite": { + "message": "Delete website" + }, + "owner": { + "message": "Owner" + }, + "addField": { + "message": "Add field" + }, + "fieldType": { + "message": "Field type" + }, + "fieldLabel": { + "message": "Field label" + }, + "add": { + "message": "Add" + }, + "textHelpText": { + "message": "Use text fields for data like security questions" + }, + "hiddenHelpText": { + "message": "Use hidden fields for sensitive data like a password" + }, + "checkBoxHelpText": { + "message": "Use checkboxes if you'd like to autofill a form's checkbox, like a remember email" + }, + "linkedHelpText": { + "message": "Use a linked field when you are experiencing autofill issues for a specific website." + }, + "linkedLabelHelpText": { + "message": "Enter the the field's html id, name, aria-label, or placeholder." + }, "folder": { "message": "Carpeta" }, @@ -418,6 +476,9 @@ "message": "Enllaçat", "description": "This describes a field that is 'linked' (related) to another field." }, + "cfTypeCheckbox": { + "message": "Checkbox" + }, "linkedValue": { "message": "Valor enllaçat", "description": "This describes a value that is 'linked' (related) to another value." @@ -1915,6 +1976,43 @@ } } }, + "cardDetails": { + "message": "Card details" + }, + "cardBrandDetails": { + "message": "$BRAND$ details", + "placeholders": { + "brand": { + "content": "$1", + "example": "Visa" + } + } + }, + "learnMoreAboutAuthenticators": { + "message": "Learn more about authenticators" + }, + "copyTOTP": { + "message": "Copy Authenticator key (TOTP)" + }, + "totpHelperTitle": { + "message": "Make 2-step verification seamless" + }, + "totpHelper": { + "message": "Bitwarden can store and fill 2-step verification codes. Copy and paste the key into this field." + }, + "totpHelperWithCapture": { + "message": "Bitwarden can store and fill 2-step verification codes. Select the camera icon to take a screenshot of this website's authenticator QR code, or copy and paste the key into this field." + }, + "premium": { + "message": "Premium", + "description": "Premium membership" + }, + "cardExpiredTitle": { + "message": "Expired card" + }, + "cardExpiredMessage": { + "message": "If you've renewed it, update the card's information" + }, "verificationRequired": { "message": "Es requereix verificació", "description": "Default title for the user verification dialog." @@ -2084,6 +2182,15 @@ "personalOwnershipPolicyInEffectImports": { "message": "Una política d'organització ha desactivat la importació d'elements a la vostra caixa forta personal." }, + "personalDetails": { + "message": "Personal details" + }, + "identification": { + "message": "Identification" + }, + "contactInfo": { + "message": "Contact information" + }, "allSends": { "message": "Tots els Send", "description": "'Sends' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." @@ -2518,6 +2625,9 @@ "generateEmail": { "message": "Genera correu electrònic" }, + "usernameGenerator": { + "message": "Username generator" + }, "spinboxBoundariesHint": { "message": "El valor ha d'estar entre $MIN$ i $MAX$.", "description": "Explains spin box minimum and maximum values to the user", @@ -3212,6 +3322,9 @@ "followTheStepsBelowToFinishLoggingIn": { "message": "Follow the steps below to finish logging in." }, + "followTheStepsBelowToFinishLoggingInWithSecurityKey": { + "message": "Follow the steps below to finish logging in with your security key." + }, "launchDuo": { "message": "Inicia Duo al navegador" }, @@ -3436,6 +3549,17 @@ "ssoError": { "message": "No free ports could be found for the sso login." }, + "securePasswordGenerated": { + "message": "Secure password generated! Don't forget to also update your password on the website." + }, + "useGeneratorHelpTextPartOne": { + "message": "Use the generator", + "description": "This will be used as part of a larger sentence, broken up to include the generator icon. The full sentence will read 'Use the generator [GENERATOR_ICON] to create a strong unique password'" + }, + "useGeneratorHelpTextPartTwo": { + "message": "to create a strong unique password", + "description": "This will be used as part of a larger sentence, broken up to include the generator icon. The full sentence will read 'Use the generator [GENERATOR_ICON] to create a strong unique password'" + }, "biometricsStatusHelptextUnlockNeeded": { "message": "Biometric unlock is unavailable because PIN or password unlock is required first." }, @@ -3514,6 +3638,27 @@ "setupTwoStepLogin": { "message": "Configura inici de sessió en dos passos" }, + "itemDetails": { + "message": "Item details" + }, + "itemName": { + "message": "Item name" + }, + "loginCredentials": { + "message": "Login credentials" + }, + "additionalOptions": { + "message": "Additional options" + }, + "itemHistory": { + "message": "Item history" + }, + "lastEdited": { + "message": "Last edited" + }, + "upload": { + "message": "Upload" + }, "newDeviceVerificationNoticeContentPage1": { "message": "Bitwarden will send a code to your account email to verify logins from new devices starting in February 2025." }, @@ -3567,5 +3712,35 @@ }, "move": { "message": "Move" + }, + "newLoginNudgeTitle": { + "message": "Save time with autofill" + }, + "newLoginNudgeBody": { + "message": "Include a Website so this login appears as an autofill suggestion." + }, + "newCardNudgeTitle": { + "message": "Seamless online checkout" + }, + "newCardNudgeBody": { + "message": "With cards, easily autofill payment forms securely and accurately." + }, + "newIdentityNudgeTitle": { + "message": "Simplify creating accounts" + }, + "newIdentityNudgeBody": { + "message": "With identities, quickly autofill long registration or contact forms." + }, + "newNoteNudgeTitle": { + "message": "Keep your sensitive data safe" + }, + "newNoteNudgeBody": { + "message": "With notes, securely store sensitive data like banking or insurance details." + }, + "newSshNudgeTitle": { + "message": "Developer-friendly SSH access" + }, + "newSshNudgeBody": { + "message": "Store your keys and connect with the SSH agent for fast, encrypted authentication." } } diff --git a/apps/desktop/src/locales/cs/messages.json b/apps/desktop/src/locales/cs/messages.json index 980b1f671ef..fba5cf3e1ce 100644 --- a/apps/desktop/src/locales/cs/messages.json +++ b/apps/desktop/src/locales/cs/messages.json @@ -393,6 +393,64 @@ "authenticatorKeyTotp": { "message": "Autentizační klíč (TOTP)" }, + "authenticatorKey": { + "message": "Ověřovací klíč" + }, + "autofillOptions": { + "message": "Volby automatického vyplňování" + }, + "websiteUri": { + "message": "Webová stránka (URI)" + }, + "websiteUriCount": { + "message": "Webová stránka (URI) $COUNT$", + "description": "Label for an input field that contains a website URI. The input field is part of a list of fields, and the count indicates the position of the field in the list.", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + } + } + }, + "websiteAdded": { + "message": "Webová stránka přidána" + }, + "addWebsite": { + "message": "Přidat webovou stránku" + }, + "deleteWebsite": { + "message": "Smazat webovou stránku" + }, + "owner": { + "message": "Vlastník" + }, + "addField": { + "message": "Přidat pole" + }, + "fieldType": { + "message": "Typ pole" + }, + "fieldLabel": { + "message": "Popis pole" + }, + "add": { + "message": "Přidat" + }, + "textHelpText": { + "message": "Použijte textová pole pro data jako např. bezpečnostní otázky" + }, + "hiddenHelpText": { + "message": "Použijte skrytá pole pro citlivá data, jako je heslo" + }, + "checkBoxHelpText": { + "message": "Použijte zaškrtávací políčka, pokud chcete automaticky vyplnit zaškrtávací políčko formuláře (např. pro zapamatování e-mailu)" + }, + "linkedHelpText": { + "message": "Použijte propojené pole, pokud máte problémy s automatickým vyplňováním na konkrétní webové stránce." + }, + "linkedLabelHelpText": { + "message": "Zadejte ID pole z HTML, název, popisek nebo zástupný znak pole." + }, "folder": { "message": "Složka" }, @@ -418,6 +476,9 @@ "message": "Propojeno", "description": "This describes a field that is 'linked' (related) to another field." }, + "cfTypeCheckbox": { + "message": "Zaškrtávací políčko" + }, "linkedValue": { "message": "Propojená hodnota", "description": "This describes a value that is 'linked' (related) to another value." @@ -1915,6 +1976,43 @@ } } }, + "cardDetails": { + "message": "Podrobnosti karty" + }, + "cardBrandDetails": { + "message": "Podrobnosti o $BRAND$", + "placeholders": { + "brand": { + "content": "$1", + "example": "Visa" + } + } + }, + "learnMoreAboutAuthenticators": { + "message": "Zjistěte více o autentizátorech" + }, + "copyTOTP": { + "message": "Kopírovat autentizační klíč (TOTP)" + }, + "totpHelperTitle": { + "message": "Bezproblémové dvoufázové ověřování" + }, + "totpHelper": { + "message": "Bitwarden umí ukládat a vyplňovat dvoufázové ověřovací kódy. Zkopírujte a vložte klíč do tohoto pole." + }, + "totpHelperWithCapture": { + "message": "Bitwarden umí ukládat a vyplňovat dvoufázové ověřovací kódy. Vyberte ikonu fotoaparátu a pořiďte snímek obrazovky ověřovacího QR kódu této webové stránky nebo klíč zkopírujte a vložte do tohoto pole." + }, + "premium": { + "message": "Premium", + "description": "Premium membership" + }, + "cardExpiredTitle": { + "message": "Prošlá karta" + }, + "cardExpiredMessage": { + "message": "Pokud jste ji obnovili, aktualizujte informace o kartě" + }, "verificationRequired": { "message": "Je vyžadováno ověření", "description": "Default title for the user verification dialog." @@ -2084,6 +2182,15 @@ "personalOwnershipPolicyInEffectImports": { "message": "Zásady organizace zablokovaly importování položek do Vašeho osobního trezoru." }, + "personalDetails": { + "message": "Osobní údaje" + }, + "identification": { + "message": "Identifikace" + }, + "contactInfo": { + "message": "Contact information" + }, "allSends": { "message": "Všechny Sends", "description": "'Sends' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." @@ -2518,6 +2625,9 @@ "generateEmail": { "message": "Vygenerovat e-mail" }, + "usernameGenerator": { + "message": "Generátor uživatelského jména" + }, "spinboxBoundariesHint": { "message": "Hodnota musí být mezi $MIN$ a $MAX$.", "description": "Explains spin box minimum and maximum values to the user", @@ -3212,6 +3322,9 @@ "followTheStepsBelowToFinishLoggingIn": { "message": "Postupujte podle kroků níže pro dokončení přihlášení." }, + "followTheStepsBelowToFinishLoggingInWithSecurityKey": { + "message": "Postupujte podle následujících kroků pro dokončení přihlášení Vaším bezpečnostním klíčem." + }, "launchDuo": { "message": "Spustit DUO v prohlížeči" }, @@ -3436,6 +3549,17 @@ "ssoError": { "message": "Pro přihlášení SSO nebyly nalezeny žádné volné porty." }, + "securePasswordGenerated": { + "message": "Bezpečné heslo bylo vygenerováno! Nezapomeňte také aktualizovat heslo na webu." + }, + "useGeneratorHelpTextPartOne": { + "message": "Použijte generátor", + "description": "This will be used as part of a larger sentence, broken up to include the generator icon. The full sentence will read 'Use the generator [GENERATOR_ICON] to create a strong unique password'" + }, + "useGeneratorHelpTextPartTwo": { + "message": "pro vytvoření silného jedinečného hesla", + "description": "This will be used as part of a larger sentence, broken up to include the generator icon. The full sentence will read 'Use the generator [GENERATOR_ICON] to create a strong unique password'" + }, "biometricsStatusHelptextUnlockNeeded": { "message": "Biometrické odemknutí je nedostupné, protože je potřeba nejprve odemknout pomocí PIN nebo hesla." }, @@ -3514,6 +3638,27 @@ "setupTwoStepLogin": { "message": "Nastavit dvoufázové přihlášení" }, + "itemDetails": { + "message": "Detaily položky" + }, + "itemName": { + "message": "Název položky" + }, + "loginCredentials": { + "message": "Přihlašovací údaje" + }, + "additionalOptions": { + "message": "Další volby" + }, + "itemHistory": { + "message": "Historie položky" + }, + "lastEdited": { + "message": "Naposledy upraveno" + }, + "upload": { + "message": "Nahrát" + }, "newDeviceVerificationNoticeContentPage1": { "message": "Bitwarden odešle kód na e-mail Vašeho účtu pro ověření přihlášení z nových zařízení počínaje únorem 2025." }, @@ -3567,5 +3712,35 @@ }, "move": { "message": "Přesunout" + }, + "newLoginNudgeTitle": { + "message": "Ušetřete čas s automatickým vyplňováním" + }, + "newLoginNudgeBody": { + "message": "Zahrne webovou stránku, takže se toto přihlášení objeví jako návrh automatického vyplňování." + }, + "newCardNudgeTitle": { + "message": "Bezproblémová online pokladna" + }, + "newCardNudgeBody": { + "message": "Karty - snadné, bezpečné a přesné vyplňování platebních formulářů." + }, + "newIdentityNudgeTitle": { + "message": "Jednodušší vytváření účtů" + }, + "newIdentityNudgeBody": { + "message": "Identity - rychlé automatické vyplňování dlouhých registračních nebo kontaktních formulářů." + }, + "newNoteNudgeTitle": { + "message": "Udržujte svá citlivá data v bezpečí" + }, + "newNoteNudgeBody": { + "message": "Poznámky - bezpečné ukládání citlivých údajů, jako jsou bankovní nebo pojišťovací údaje." + }, + "newSshNudgeTitle": { + "message": "Přístup SSH pro vývojáře" + }, + "newSshNudgeBody": { + "message": "Uložte své klíče a připojte se k SSH agentovi pro rychlé a šifrované ověření." } } diff --git a/apps/desktop/src/locales/cy/messages.json b/apps/desktop/src/locales/cy/messages.json index d911e190591..efcf558bbed 100644 --- a/apps/desktop/src/locales/cy/messages.json +++ b/apps/desktop/src/locales/cy/messages.json @@ -393,6 +393,64 @@ "authenticatorKeyTotp": { "message": "Authenticator key (TOTP)" }, + "authenticatorKey": { + "message": "Authenticator key" + }, + "autofillOptions": { + "message": "Autofill options" + }, + "websiteUri": { + "message": "Website (URI)" + }, + "websiteUriCount": { + "message": "Website (URI) $COUNT$", + "description": "Label for an input field that contains a website URI. The input field is part of a list of fields, and the count indicates the position of the field in the list.", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + } + } + }, + "websiteAdded": { + "message": "Website added" + }, + "addWebsite": { + "message": "Add website" + }, + "deleteWebsite": { + "message": "Delete website" + }, + "owner": { + "message": "Owner" + }, + "addField": { + "message": "Add field" + }, + "fieldType": { + "message": "Field type" + }, + "fieldLabel": { + "message": "Field label" + }, + "add": { + "message": "Add" + }, + "textHelpText": { + "message": "Use text fields for data like security questions" + }, + "hiddenHelpText": { + "message": "Use hidden fields for sensitive data like a password" + }, + "checkBoxHelpText": { + "message": "Use checkboxes if you'd like to autofill a form's checkbox, like a remember email" + }, + "linkedHelpText": { + "message": "Use a linked field when you are experiencing autofill issues for a specific website." + }, + "linkedLabelHelpText": { + "message": "Enter the the field's html id, name, aria-label, or placeholder." + }, "folder": { "message": "Folder" }, @@ -418,6 +476,9 @@ "message": "Linked", "description": "This describes a field that is 'linked' (related) to another field." }, + "cfTypeCheckbox": { + "message": "Checkbox" + }, "linkedValue": { "message": "Linked value", "description": "This describes a value that is 'linked' (related) to another value." @@ -1915,6 +1976,43 @@ } } }, + "cardDetails": { + "message": "Card details" + }, + "cardBrandDetails": { + "message": "$BRAND$ details", + "placeholders": { + "brand": { + "content": "$1", + "example": "Visa" + } + } + }, + "learnMoreAboutAuthenticators": { + "message": "Learn more about authenticators" + }, + "copyTOTP": { + "message": "Copy Authenticator key (TOTP)" + }, + "totpHelperTitle": { + "message": "Make 2-step verification seamless" + }, + "totpHelper": { + "message": "Bitwarden can store and fill 2-step verification codes. Copy and paste the key into this field." + }, + "totpHelperWithCapture": { + "message": "Bitwarden can store and fill 2-step verification codes. Select the camera icon to take a screenshot of this website's authenticator QR code, or copy and paste the key into this field." + }, + "premium": { + "message": "Premium", + "description": "Premium membership" + }, + "cardExpiredTitle": { + "message": "Expired card" + }, + "cardExpiredMessage": { + "message": "If you've renewed it, update the card's information" + }, "verificationRequired": { "message": "Verification required", "description": "Default title for the user verification dialog." @@ -2084,6 +2182,15 @@ "personalOwnershipPolicyInEffectImports": { "message": "An organization policy has blocked importing items into your individual vault." }, + "personalDetails": { + "message": "Personal details" + }, + "identification": { + "message": "Identification" + }, + "contactInfo": { + "message": "Contact information" + }, "allSends": { "message": "All Sends", "description": "'Sends' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." @@ -2518,6 +2625,9 @@ "generateEmail": { "message": "Generate email" }, + "usernameGenerator": { + "message": "Username generator" + }, "spinboxBoundariesHint": { "message": "Value must be between $MIN$ and $MAX$.", "description": "Explains spin box minimum and maximum values to the user", @@ -3212,6 +3322,9 @@ "followTheStepsBelowToFinishLoggingIn": { "message": "Follow the steps below to finish logging in." }, + "followTheStepsBelowToFinishLoggingInWithSecurityKey": { + "message": "Follow the steps below to finish logging in with your security key." + }, "launchDuo": { "message": "Launch Duo in Browser" }, @@ -3436,6 +3549,17 @@ "ssoError": { "message": "No free ports could be found for the sso login." }, + "securePasswordGenerated": { + "message": "Secure password generated! Don't forget to also update your password on the website." + }, + "useGeneratorHelpTextPartOne": { + "message": "Use the generator", + "description": "This will be used as part of a larger sentence, broken up to include the generator icon. The full sentence will read 'Use the generator [GENERATOR_ICON] to create a strong unique password'" + }, + "useGeneratorHelpTextPartTwo": { + "message": "to create a strong unique password", + "description": "This will be used as part of a larger sentence, broken up to include the generator icon. The full sentence will read 'Use the generator [GENERATOR_ICON] to create a strong unique password'" + }, "biometricsStatusHelptextUnlockNeeded": { "message": "Biometric unlock is unavailable because PIN or password unlock is required first." }, @@ -3514,6 +3638,27 @@ "setupTwoStepLogin": { "message": "Set up two-step login" }, + "itemDetails": { + "message": "Item details" + }, + "itemName": { + "message": "Item name" + }, + "loginCredentials": { + "message": "Login credentials" + }, + "additionalOptions": { + "message": "Additional options" + }, + "itemHistory": { + "message": "Item history" + }, + "lastEdited": { + "message": "Last edited" + }, + "upload": { + "message": "Upload" + }, "newDeviceVerificationNoticeContentPage1": { "message": "Bitwarden will send a code to your account email to verify logins from new devices starting in February 2025." }, @@ -3567,5 +3712,35 @@ }, "move": { "message": "Move" + }, + "newLoginNudgeTitle": { + "message": "Save time with autofill" + }, + "newLoginNudgeBody": { + "message": "Include a Website so this login appears as an autofill suggestion." + }, + "newCardNudgeTitle": { + "message": "Seamless online checkout" + }, + "newCardNudgeBody": { + "message": "With cards, easily autofill payment forms securely and accurately." + }, + "newIdentityNudgeTitle": { + "message": "Simplify creating accounts" + }, + "newIdentityNudgeBody": { + "message": "With identities, quickly autofill long registration or contact forms." + }, + "newNoteNudgeTitle": { + "message": "Keep your sensitive data safe" + }, + "newNoteNudgeBody": { + "message": "With notes, securely store sensitive data like banking or insurance details." + }, + "newSshNudgeTitle": { + "message": "Developer-friendly SSH access" + }, + "newSshNudgeBody": { + "message": "Store your keys and connect with the SSH agent for fast, encrypted authentication." } } diff --git a/apps/desktop/src/locales/da/messages.json b/apps/desktop/src/locales/da/messages.json index d36e13dcc83..f7b0c33707a 100644 --- a/apps/desktop/src/locales/da/messages.json +++ b/apps/desktop/src/locales/da/messages.json @@ -393,6 +393,64 @@ "authenticatorKeyTotp": { "message": "Godkendelsesnøgle (TOTP)" }, + "authenticatorKey": { + "message": "Authenticator key" + }, + "autofillOptions": { + "message": "Autofill options" + }, + "websiteUri": { + "message": "Website (URI)" + }, + "websiteUriCount": { + "message": "Website (URI) $COUNT$", + "description": "Label for an input field that contains a website URI. The input field is part of a list of fields, and the count indicates the position of the field in the list.", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + } + } + }, + "websiteAdded": { + "message": "Website added" + }, + "addWebsite": { + "message": "Add website" + }, + "deleteWebsite": { + "message": "Delete website" + }, + "owner": { + "message": "Owner" + }, + "addField": { + "message": "Add field" + }, + "fieldType": { + "message": "Field type" + }, + "fieldLabel": { + "message": "Field label" + }, + "add": { + "message": "Add" + }, + "textHelpText": { + "message": "Use text fields for data like security questions" + }, + "hiddenHelpText": { + "message": "Use hidden fields for sensitive data like a password" + }, + "checkBoxHelpText": { + "message": "Use checkboxes if you'd like to autofill a form's checkbox, like a remember email" + }, + "linkedHelpText": { + "message": "Use a linked field when you are experiencing autofill issues for a specific website." + }, + "linkedLabelHelpText": { + "message": "Enter the the field's html id, name, aria-label, or placeholder." + }, "folder": { "message": "Mappe" }, @@ -418,6 +476,9 @@ "message": "Forbundet", "description": "This describes a field that is 'linked' (related) to another field." }, + "cfTypeCheckbox": { + "message": "Checkbox" + }, "linkedValue": { "message": "Forbundet værdi", "description": "This describes a value that is 'linked' (related) to another value." @@ -1915,6 +1976,43 @@ } } }, + "cardDetails": { + "message": "Card details" + }, + "cardBrandDetails": { + "message": "$BRAND$ details", + "placeholders": { + "brand": { + "content": "$1", + "example": "Visa" + } + } + }, + "learnMoreAboutAuthenticators": { + "message": "Learn more about authenticators" + }, + "copyTOTP": { + "message": "Copy Authenticator key (TOTP)" + }, + "totpHelperTitle": { + "message": "Make 2-step verification seamless" + }, + "totpHelper": { + "message": "Bitwarden can store and fill 2-step verification codes. Copy and paste the key into this field." + }, + "totpHelperWithCapture": { + "message": "Bitwarden can store and fill 2-step verification codes. Select the camera icon to take a screenshot of this website's authenticator QR code, or copy and paste the key into this field." + }, + "premium": { + "message": "Premium", + "description": "Premium membership" + }, + "cardExpiredTitle": { + "message": "Expired card" + }, + "cardExpiredMessage": { + "message": "If you've renewed it, update the card's information" + }, "verificationRequired": { "message": "Bekræftelse kræves", "description": "Default title for the user verification dialog." @@ -2084,6 +2182,15 @@ "personalOwnershipPolicyInEffectImports": { "message": "En organisationspolitik hindrer import af emner til den personlige boks." }, + "personalDetails": { + "message": "Personal details" + }, + "identification": { + "message": "Identification" + }, + "contactInfo": { + "message": "Contact information" + }, "allSends": { "message": "Alle Send", "description": "'Sends' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." @@ -2518,6 +2625,9 @@ "generateEmail": { "message": "Generér e-mail" }, + "usernameGenerator": { + "message": "Username generator" + }, "spinboxBoundariesHint": { "message": "Værdi skal være mellem $MIN$ og $MAX$.", "description": "Explains spin box minimum and maximum values to the user", @@ -3212,6 +3322,9 @@ "followTheStepsBelowToFinishLoggingIn": { "message": "Follow the steps below to finish logging in." }, + "followTheStepsBelowToFinishLoggingInWithSecurityKey": { + "message": "Follow the steps below to finish logging in with your security key." + }, "launchDuo": { "message": "Start Duo i webbrowser" }, @@ -3436,6 +3549,17 @@ "ssoError": { "message": "Ingen ledige porte fundet til SSO-login." }, + "securePasswordGenerated": { + "message": "Secure password generated! Don't forget to also update your password on the website." + }, + "useGeneratorHelpTextPartOne": { + "message": "Use the generator", + "description": "This will be used as part of a larger sentence, broken up to include the generator icon. The full sentence will read 'Use the generator [GENERATOR_ICON] to create a strong unique password'" + }, + "useGeneratorHelpTextPartTwo": { + "message": "to create a strong unique password", + "description": "This will be used as part of a larger sentence, broken up to include the generator icon. The full sentence will read 'Use the generator [GENERATOR_ICON] to create a strong unique password'" + }, "biometricsStatusHelptextUnlockNeeded": { "message": "Biometrisk oplåsning er utilgængelig, da PIN- eller adgangskode kræves først." }, @@ -3514,6 +3638,27 @@ "setupTwoStepLogin": { "message": "Opsæt totrins-login" }, + "itemDetails": { + "message": "Item details" + }, + "itemName": { + "message": "Item name" + }, + "loginCredentials": { + "message": "Login credentials" + }, + "additionalOptions": { + "message": "Additional options" + }, + "itemHistory": { + "message": "Item history" + }, + "lastEdited": { + "message": "Last edited" + }, + "upload": { + "message": "Upload" + }, "newDeviceVerificationNoticeContentPage1": { "message": "Startende i februar 2025, sender Bitwarden en kode til kontoe-mailadressen for at bekræfte logins fra nye enheder." }, @@ -3567,5 +3712,35 @@ }, "move": { "message": "Move" + }, + "newLoginNudgeTitle": { + "message": "Save time with autofill" + }, + "newLoginNudgeBody": { + "message": "Include a Website so this login appears as an autofill suggestion." + }, + "newCardNudgeTitle": { + "message": "Seamless online checkout" + }, + "newCardNudgeBody": { + "message": "With cards, easily autofill payment forms securely and accurately." + }, + "newIdentityNudgeTitle": { + "message": "Simplify creating accounts" + }, + "newIdentityNudgeBody": { + "message": "With identities, quickly autofill long registration or contact forms." + }, + "newNoteNudgeTitle": { + "message": "Keep your sensitive data safe" + }, + "newNoteNudgeBody": { + "message": "With notes, securely store sensitive data like banking or insurance details." + }, + "newSshNudgeTitle": { + "message": "Developer-friendly SSH access" + }, + "newSshNudgeBody": { + "message": "Store your keys and connect with the SSH agent for fast, encrypted authentication." } } diff --git a/apps/desktop/src/locales/de/messages.json b/apps/desktop/src/locales/de/messages.json index 442023608ee..b1c129fd488 100644 --- a/apps/desktop/src/locales/de/messages.json +++ b/apps/desktop/src/locales/de/messages.json @@ -393,6 +393,64 @@ "authenticatorKeyTotp": { "message": "Authentifizierungsschlüssel (TOTP)" }, + "authenticatorKey": { + "message": "Authenticator-Schlüssel" + }, + "autofillOptions": { + "message": "Auto-Ausfüllen-Optionen" + }, + "websiteUri": { + "message": "Webseite (URI)" + }, + "websiteUriCount": { + "message": "Webseite (URI) $COUNT$", + "description": "Label for an input field that contains a website URI. The input field is part of a list of fields, and the count indicates the position of the field in the list.", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + } + } + }, + "websiteAdded": { + "message": "Webseite hinzugefügt" + }, + "addWebsite": { + "message": "Webseite hinzufügen" + }, + "deleteWebsite": { + "message": "Webseite löschen" + }, + "owner": { + "message": "Besitzer" + }, + "addField": { + "message": "Feld hinzufügen" + }, + "fieldType": { + "message": "Feldtyp" + }, + "fieldLabel": { + "message": "Feldbezeichnung" + }, + "add": { + "message": "Hinzufügen" + }, + "textHelpText": { + "message": "Verwende Textfelder für Daten wie Sicherheitsfragen" + }, + "hiddenHelpText": { + "message": "Verwende versteckte Felder für vertrauliche Daten wie ein Passwort" + }, + "checkBoxHelpText": { + "message": "Verwende Kontrollkästchen, wenn du das Kontrollkästchen eines Formulars automatisch ausfüllen möchtest, wie eine Erinnerungs-E-Mail" + }, + "linkedHelpText": { + "message": "Verwende ein verknüpftes Feld, wenn du Probleme mit dem automatischen Ausfüllen einer bestimmten Webseite hast." + }, + "linkedLabelHelpText": { + "message": "Gib die HTML-ID des Felds, Name, Aria-Label oder einen Platzhalter ein." + }, "folder": { "message": "Ordner" }, @@ -418,6 +476,9 @@ "message": "Verknüpft", "description": "This describes a field that is 'linked' (related) to another field." }, + "cfTypeCheckbox": { + "message": "Kontrollkästchen" + }, "linkedValue": { "message": "Verknüpfter Wert", "description": "This describes a value that is 'linked' (related) to another value." @@ -1915,6 +1976,43 @@ } } }, + "cardDetails": { + "message": "Kartendetails" + }, + "cardBrandDetails": { + "message": "$BRAND$-Details", + "placeholders": { + "brand": { + "content": "$1", + "example": "Visa" + } + } + }, + "learnMoreAboutAuthenticators": { + "message": "Erfahre mehr über Authenticator-Apps" + }, + "copyTOTP": { + "message": "Authenticator-Schlüssel kopieren (TOTP)" + }, + "totpHelperTitle": { + "message": "Zwei-Faktor-Authentifizierung nahtlos gestalten" + }, + "totpHelper": { + "message": "Bitwarden kann Zwei-Faktor-Authentifizierungsscodes speichern und ausfüllen. Kopiere und füge den Schlüssel in dieses Feld ein." + }, + "totpHelperWithCapture": { + "message": "Bitwarden kann Zwei-Faktor-Authentifizierungsscodes speichern und ausfüllen. Wähle das Kamerasymbol aus, um einen Screenshot des Authenticator-QR-Codes dieser Website zu machen oder kopiere und füge den Schlüssel in dieses Feld ein." + }, + "premium": { + "message": "Premium", + "description": "Premium membership" + }, + "cardExpiredTitle": { + "message": "Abgelaufene Karte" + }, + "cardExpiredMessage": { + "message": "Wenn du die Karte erneuert hast, aktualisiere die Angaben zur Karte" + }, "verificationRequired": { "message": "Verifizierung erforderlich", "description": "Default title for the user verification dialog." @@ -2084,6 +2182,15 @@ "personalOwnershipPolicyInEffectImports": { "message": "Eine Organisationsrichtlinie hat das Importieren von Einträgen in deinen persönlichen Tresor deaktiviert." }, + "personalDetails": { + "message": "Persönliche Details" + }, + "identification": { + "message": "Identifikation" + }, + "contactInfo": { + "message": "Kontaktinformationen" + }, "allSends": { "message": "Alle Sends", "description": "'Sends' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." @@ -2518,6 +2625,9 @@ "generateEmail": { "message": "E-Mail generieren" }, + "usernameGenerator": { + "message": "Benutzernamen-Generator" + }, "spinboxBoundariesHint": { "message": "Wert muss zwischen $MIN$ und $MAX$ liegen.", "description": "Explains spin box minimum and maximum values to the user", @@ -3212,6 +3322,9 @@ "followTheStepsBelowToFinishLoggingIn": { "message": "Folge den Schritten unten, um die Anmeldung abzuschließen." }, + "followTheStepsBelowToFinishLoggingInWithSecurityKey": { + "message": "Folge den Schritten unten, um die Anmeldung mit deinem Sicherheitsschlüssel abzuschließen." + }, "launchDuo": { "message": "Duo im Browser starten" }, @@ -3436,6 +3549,17 @@ "ssoError": { "message": "Es konnten keine freien Ports für die SSO-Anmeldung gefunden werden." }, + "securePasswordGenerated": { + "message": "Sicheres Passwort generiert! Vergiss nicht, auch dein Passwort auf der Website zu aktualisieren." + }, + "useGeneratorHelpTextPartOne": { + "message": "Verwende den Generator", + "description": "This will be used as part of a larger sentence, broken up to include the generator icon. The full sentence will read 'Use the generator [GENERATOR_ICON] to create a strong unique password'" + }, + "useGeneratorHelpTextPartTwo": { + "message": ", um ein starkes einzigartiges Passwort zu erstellen", + "description": "This will be used as part of a larger sentence, broken up to include the generator icon. The full sentence will read 'Use the generator [GENERATOR_ICON] to create a strong unique password'" + }, "biometricsStatusHelptextUnlockNeeded": { "message": "Biometrisches Entsperren ist nicht verfügbar, da zuerst mit PIN oder Passwort entsperrt werden muss." }, @@ -3514,6 +3638,27 @@ "setupTwoStepLogin": { "message": "Zwei-Faktor-Authentifizierung einrichten" }, + "itemDetails": { + "message": "Eintrag-Details" + }, + "itemName": { + "message": "Eintrags-Name" + }, + "loginCredentials": { + "message": "Zugangsdaten" + }, + "additionalOptions": { + "message": "Weitere Optionen" + }, + "itemHistory": { + "message": "Eintragsverlauf" + }, + "lastEdited": { + "message": "Zuletzt bearbeitet" + }, + "upload": { + "message": "Hochladen" + }, "newDeviceVerificationNoticeContentPage1": { "message": "Ab Februar 2025 wird Bitwarden einen Code an deine Konto-E-Mail-Adresse senden, um Anmeldungen von neuen Geräten zu verifizieren." }, @@ -3567,5 +3712,35 @@ }, "move": { "message": "Verschieben" + }, + "newLoginNudgeTitle": { + "message": "Save time with autofill" + }, + "newLoginNudgeBody": { + "message": "Include a Website so this login appears as an autofill suggestion." + }, + "newCardNudgeTitle": { + "message": "Seamless online checkout" + }, + "newCardNudgeBody": { + "message": "With cards, easily autofill payment forms securely and accurately." + }, + "newIdentityNudgeTitle": { + "message": "Simplify creating accounts" + }, + "newIdentityNudgeBody": { + "message": "With identities, quickly autofill long registration or contact forms." + }, + "newNoteNudgeTitle": { + "message": "Keep your sensitive data safe" + }, + "newNoteNudgeBody": { + "message": "With notes, securely store sensitive data like banking or insurance details." + }, + "newSshNudgeTitle": { + "message": "Developer-friendly SSH access" + }, + "newSshNudgeBody": { + "message": "Store your keys and connect with the SSH agent for fast, encrypted authentication." } } diff --git a/apps/desktop/src/locales/el/messages.json b/apps/desktop/src/locales/el/messages.json index 19ef7d956ba..a91b678a04c 100644 --- a/apps/desktop/src/locales/el/messages.json +++ b/apps/desktop/src/locales/el/messages.json @@ -393,6 +393,64 @@ "authenticatorKeyTotp": { "message": "Κλειδί αυθεντικοποίησης (TOTP)" }, + "authenticatorKey": { + "message": "Authenticator key" + }, + "autofillOptions": { + "message": "Autofill options" + }, + "websiteUri": { + "message": "Website (URI)" + }, + "websiteUriCount": { + "message": "Website (URI) $COUNT$", + "description": "Label for an input field that contains a website URI. The input field is part of a list of fields, and the count indicates the position of the field in the list.", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + } + } + }, + "websiteAdded": { + "message": "Website added" + }, + "addWebsite": { + "message": "Add website" + }, + "deleteWebsite": { + "message": "Delete website" + }, + "owner": { + "message": "Owner" + }, + "addField": { + "message": "Add field" + }, + "fieldType": { + "message": "Field type" + }, + "fieldLabel": { + "message": "Field label" + }, + "add": { + "message": "Add" + }, + "textHelpText": { + "message": "Use text fields for data like security questions" + }, + "hiddenHelpText": { + "message": "Use hidden fields for sensitive data like a password" + }, + "checkBoxHelpText": { + "message": "Use checkboxes if you'd like to autofill a form's checkbox, like a remember email" + }, + "linkedHelpText": { + "message": "Use a linked field when you are experiencing autofill issues for a specific website." + }, + "linkedLabelHelpText": { + "message": "Enter the the field's html id, name, aria-label, or placeholder." + }, "folder": { "message": "Φάκελος" }, @@ -418,6 +476,9 @@ "message": "Συνδεδεμένο", "description": "This describes a field that is 'linked' (related) to another field." }, + "cfTypeCheckbox": { + "message": "Checkbox" + }, "linkedValue": { "message": "Συνδεδεμένη τιμή", "description": "This describes a value that is 'linked' (related) to another value." @@ -1915,6 +1976,43 @@ } } }, + "cardDetails": { + "message": "Card details" + }, + "cardBrandDetails": { + "message": "$BRAND$ details", + "placeholders": { + "brand": { + "content": "$1", + "example": "Visa" + } + } + }, + "learnMoreAboutAuthenticators": { + "message": "Learn more about authenticators" + }, + "copyTOTP": { + "message": "Copy Authenticator key (TOTP)" + }, + "totpHelperTitle": { + "message": "Make 2-step verification seamless" + }, + "totpHelper": { + "message": "Bitwarden can store and fill 2-step verification codes. Copy and paste the key into this field." + }, + "totpHelperWithCapture": { + "message": "Bitwarden can store and fill 2-step verification codes. Select the camera icon to take a screenshot of this website's authenticator QR code, or copy and paste the key into this field." + }, + "premium": { + "message": "Premium", + "description": "Premium membership" + }, + "cardExpiredTitle": { + "message": "Expired card" + }, + "cardExpiredMessage": { + "message": "If you've renewed it, update the card's information" + }, "verificationRequired": { "message": "Απαιτείται επαλήθευση", "description": "Default title for the user verification dialog." @@ -2084,6 +2182,15 @@ "personalOwnershipPolicyInEffectImports": { "message": "Μια πολιτική οργανισμού έχει αποτρέψει την εισαγωγή αντικειμένων στο ατομικό σας θησαυ/κιο." }, + "personalDetails": { + "message": "Personal details" + }, + "identification": { + "message": "Identification" + }, + "contactInfo": { + "message": "Contact information" + }, "allSends": { "message": "Όλα τα Sends", "description": "'Sends' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." @@ -2518,6 +2625,9 @@ "generateEmail": { "message": "Δημιουργία διεύθυνσης ηλ. ταχυδρομείου" }, + "usernameGenerator": { + "message": "Username generator" + }, "spinboxBoundariesHint": { "message": "Η τιμή πρέπει να είναι μεταξύ $MIN$ και $MAX$.", "description": "Explains spin box minimum and maximum values to the user", @@ -3212,6 +3322,9 @@ "followTheStepsBelowToFinishLoggingIn": { "message": "Follow the steps below to finish logging in." }, + "followTheStepsBelowToFinishLoggingInWithSecurityKey": { + "message": "Follow the steps below to finish logging in with your security key." + }, "launchDuo": { "message": "Εκκίνηση Duo στον περιηγητή" }, @@ -3436,6 +3549,17 @@ "ssoError": { "message": "Δεν βρέθηκαν ελεύθερες θύρες για τη σύνδεση sso." }, + "securePasswordGenerated": { + "message": "Secure password generated! Don't forget to also update your password on the website." + }, + "useGeneratorHelpTextPartOne": { + "message": "Use the generator", + "description": "This will be used as part of a larger sentence, broken up to include the generator icon. The full sentence will read 'Use the generator [GENERATOR_ICON] to create a strong unique password'" + }, + "useGeneratorHelpTextPartTwo": { + "message": "to create a strong unique password", + "description": "This will be used as part of a larger sentence, broken up to include the generator icon. The full sentence will read 'Use the generator [GENERATOR_ICON] to create a strong unique password'" + }, "biometricsStatusHelptextUnlockNeeded": { "message": "Το βιομετρικό ξεκλείδωμα δεν είναι διαθέσιμο επειδή απαιτείται πρώτα το ξεκλείδωμα με PIN ή κωδικό πρόσβασης." }, @@ -3514,6 +3638,27 @@ "setupTwoStepLogin": { "message": "Ρύθμιση σύνδεσης δύο βημάτων" }, + "itemDetails": { + "message": "Item details" + }, + "itemName": { + "message": "Item name" + }, + "loginCredentials": { + "message": "Login credentials" + }, + "additionalOptions": { + "message": "Additional options" + }, + "itemHistory": { + "message": "Item history" + }, + "lastEdited": { + "message": "Last edited" + }, + "upload": { + "message": "Upload" + }, "newDeviceVerificationNoticeContentPage1": { "message": "Το Bitwarden θα στείλει έναν κωδικό στο email του λογαριασμού σας για να επαληθεύσει τις συνδέσεις από νέες συσκευές ξεκινώντας από τον Φεβρουάριο του 2025." }, @@ -3567,5 +3712,35 @@ }, "move": { "message": "Move" + }, + "newLoginNudgeTitle": { + "message": "Save time with autofill" + }, + "newLoginNudgeBody": { + "message": "Include a Website so this login appears as an autofill suggestion." + }, + "newCardNudgeTitle": { + "message": "Seamless online checkout" + }, + "newCardNudgeBody": { + "message": "With cards, easily autofill payment forms securely and accurately." + }, + "newIdentityNudgeTitle": { + "message": "Simplify creating accounts" + }, + "newIdentityNudgeBody": { + "message": "With identities, quickly autofill long registration or contact forms." + }, + "newNoteNudgeTitle": { + "message": "Keep your sensitive data safe" + }, + "newNoteNudgeBody": { + "message": "With notes, securely store sensitive data like banking or insurance details." + }, + "newSshNudgeTitle": { + "message": "Developer-friendly SSH access" + }, + "newSshNudgeBody": { + "message": "Store your keys and connect with the SSH agent for fast, encrypted authentication." } } diff --git a/apps/desktop/src/locales/en/messages.json b/apps/desktop/src/locales/en/messages.json index d309b66b7bf..3c87b80ce8f 100644 --- a/apps/desktop/src/locales/en/messages.json +++ b/apps/desktop/src/locales/en/messages.json @@ -393,6 +393,64 @@ "authenticatorKeyTotp": { "message": "Authenticator key (TOTP)" }, + "authenticatorKey": { + "message": "Authenticator key" + }, + "autofillOptions": { + "message": "Autofill options" + }, + "websiteUri": { + "message": "Website (URI)" + }, + "websiteUriCount": { + "message": "Website (URI) $COUNT$", + "description": "Label for an input field that contains a website URI. The input field is part of a list of fields, and the count indicates the position of the field in the list.", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + } + } + }, + "websiteAdded": { + "message": "Website added" + }, + "addWebsite": { + "message": "Add website" + }, + "deleteWebsite": { + "message": "Delete website" + }, + "owner": { + "message": "Owner" + }, + "addField": { + "message": "Add field" + }, + "fieldType": { + "message": "Field type" + }, + "fieldLabel": { + "message": "Field label" + }, + "add": { + "message": "Add" + }, + "textHelpText": { + "message": "Use text fields for data like security questions" + }, + "hiddenHelpText": { + "message": "Use hidden fields for sensitive data like a password" + }, + "checkBoxHelpText": { + "message": "Use checkboxes if you'd like to autofill a form's checkbox, like a remember email" + }, + "linkedHelpText": { + "message": "Use a linked field when you are experiencing autofill issues for a specific website." + }, + "linkedLabelHelpText": { + "message": "Enter the the field's html id, name, aria-label, or placeholder." + }, "folder": { "message": "Folder" }, @@ -418,6 +476,9 @@ "message": "Linked", "description": "This describes a field that is 'linked' (related) to another field." }, + "cfTypeCheckbox": { + "message": "Checkbox" + }, "linkedValue": { "message": "Linked value", "description": "This describes a value that is 'linked' (related) to another value." @@ -1922,6 +1983,43 @@ } } }, + "cardDetails": { + "message": "Card details" + }, + "cardBrandDetails": { + "message": "$BRAND$ details", + "placeholders": { + "brand": { + "content": "$1", + "example": "Visa" + } + } + }, + "learnMoreAboutAuthenticators": { + "message": "Learn more about authenticators" + }, + "copyTOTP": { + "message": "Copy Authenticator key (TOTP)" + }, + "totpHelperTitle": { + "message": "Make 2-step verification seamless" + }, + "totpHelper": { + "message": "Bitwarden can store and fill 2-step verification codes. Copy and paste the key into this field." + }, + "totpHelperWithCapture": { + "message": "Bitwarden can store and fill 2-step verification codes. Select the camera icon to take a screenshot of this website's authenticator QR code, or copy and paste the key into this field." + }, + "premium": { + "message": "Premium", + "description": "Premium membership" + }, + "cardExpiredTitle": { + "message": "Expired card" + }, + "cardExpiredMessage": { + "message": "If you've renewed it, update the card's information" + }, "verificationRequired": { "message": "Verification required", "description": "Default title for the user verification dialog." @@ -2091,6 +2189,15 @@ "personalOwnershipPolicyInEffectImports": { "message": "An organization policy has blocked importing items into your individual vault." }, + "personalDetails": { + "message": "Personal details" + }, + "identification": { + "message": "Identification" + }, + "contactInfo": { + "message": "Contact information" + }, "allSends": { "message": "All Sends", "description": "'Sends' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." @@ -2525,6 +2632,9 @@ "generateEmail": { "message": "Generate email" }, + "usernameGenerator": { + "message": "Username generator" + }, "spinboxBoundariesHint": { "message": "Value must be between $MIN$ and $MAX$.", "description": "Explains spin box minimum and maximum values to the user", @@ -3219,6 +3329,9 @@ "followTheStepsBelowToFinishLoggingIn": { "message": "Follow the steps below to finish logging in." }, + "followTheStepsBelowToFinishLoggingInWithSecurityKey": { + "message": "Follow the steps below to finish logging in with your security key." + }, "launchDuo": { "message": "Launch Duo in Browser" }, @@ -3443,6 +3556,17 @@ "ssoError": { "message": "No free ports could be found for the sso login." }, + "securePasswordGenerated": { + "message": "Secure password generated! Don't forget to also update your password on the website." + }, + "useGeneratorHelpTextPartOne": { + "message": "Use the generator", + "description": "This will be used as part of a larger sentence, broken up to include the generator icon. The full sentence will read 'Use the generator [GENERATOR_ICON] to create a strong unique password'" + }, + "useGeneratorHelpTextPartTwo": { + "message": "to create a strong unique password", + "description": "This will be used as part of a larger sentence, broken up to include the generator icon. The full sentence will read 'Use the generator [GENERATOR_ICON] to create a strong unique password'" + }, "biometricsStatusHelptextUnlockNeeded": { "message": "Biometric unlock is unavailable because PIN or password unlock is required first." }, @@ -3521,6 +3645,27 @@ "setupTwoStepLogin": { "message": "Set up two-step login" }, + "itemDetails": { + "message": "Item details" + }, + "itemName": { + "message": "Item name" + }, + "loginCredentials": { + "message": "Login credentials" + }, + "additionalOptions": { + "message": "Additional options" + }, + "itemHistory": { + "message": "Item history" + }, + "lastEdited": { + "message": "Last edited" + }, + "upload": { + "message": "Upload" + }, "newDeviceVerificationNoticeContentPage1": { "message": "Bitwarden will send a code to your account email to verify logins from new devices starting in February 2025." }, @@ -3574,5 +3719,35 @@ }, "move": { "message": "Move" + }, + "newLoginNudgeTitle": { + "message": "Save time with autofill" + }, + "newLoginNudgeBody": { + "message": "Include a Website so this login appears as an autofill suggestion." + }, + "newCardNudgeTitle": { + "message": "Seamless online checkout" + }, + "newCardNudgeBody": { + "message": "With cards, easily autofill payment forms securely and accurately." + }, + "newIdentityNudgeTitle": { + "message": "Simplify creating accounts" + }, + "newIdentityNudgeBody": { + "message": "With identities, quickly autofill long registration or contact forms." + }, + "newNoteNudgeTitle": { + "message": "Keep your sensitive data safe" + }, + "newNoteNudgeBody": { + "message": "With notes, securely store sensitive data like banking or insurance details." + }, + "newSshNudgeTitle": { + "message": "Developer-friendly SSH access" + }, + "newSshNudgeBody": { + "message": "Store your keys and connect with the SSH agent for fast, encrypted authentication." } } diff --git a/apps/desktop/src/locales/en_GB/messages.json b/apps/desktop/src/locales/en_GB/messages.json index ef5cfa5b46d..bcc9dd74934 100644 --- a/apps/desktop/src/locales/en_GB/messages.json +++ b/apps/desktop/src/locales/en_GB/messages.json @@ -393,6 +393,64 @@ "authenticatorKeyTotp": { "message": "Authenticator key (TOTP)" }, + "authenticatorKey": { + "message": "Authenticator key" + }, + "autofillOptions": { + "message": "Autofill options" + }, + "websiteUri": { + "message": "Website (URI)" + }, + "websiteUriCount": { + "message": "Website (URI) $COUNT$", + "description": "Label for an input field that contains a website URI. The input field is part of a list of fields, and the count indicates the position of the field in the list.", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + } + } + }, + "websiteAdded": { + "message": "Website added" + }, + "addWebsite": { + "message": "Add website" + }, + "deleteWebsite": { + "message": "Delete website" + }, + "owner": { + "message": "Owner" + }, + "addField": { + "message": "Add field" + }, + "fieldType": { + "message": "Field type" + }, + "fieldLabel": { + "message": "Field label" + }, + "add": { + "message": "Add" + }, + "textHelpText": { + "message": "Use text fields for data like security questions" + }, + "hiddenHelpText": { + "message": "Use hidden fields for sensitive data like a password" + }, + "checkBoxHelpText": { + "message": "Use checkboxes if you'd like to autofill a form's checkbox, like a remember email" + }, + "linkedHelpText": { + "message": "Use a linked field when you are experiencing autofill issues for a specific website." + }, + "linkedLabelHelpText": { + "message": "Enter the field's html id, name, aria-label, or placeholder." + }, "folder": { "message": "Folder" }, @@ -418,6 +476,9 @@ "message": "Linked", "description": "This describes a field that is 'linked' (related) to another field." }, + "cfTypeCheckbox": { + "message": "Checkbox" + }, "linkedValue": { "message": "Linked value", "description": "This describes a value that is 'linked' (related) to another value." @@ -1915,6 +1976,43 @@ } } }, + "cardDetails": { + "message": "Card details" + }, + "cardBrandDetails": { + "message": "$BRAND$ details", + "placeholders": { + "brand": { + "content": "$1", + "example": "Visa" + } + } + }, + "learnMoreAboutAuthenticators": { + "message": "Learn more about authenticators" + }, + "copyTOTP": { + "message": "Copy Authenticator key (TOTP)" + }, + "totpHelperTitle": { + "message": "Make 2-step verification seamless" + }, + "totpHelper": { + "message": "Bitwarden can store and fill 2-step verification codes. Copy and paste the key into this field." + }, + "totpHelperWithCapture": { + "message": "Bitwarden can store and fill 2-step verification codes. Select the camera icon to take a screenshot of this website's authenticator QR code, or copy and paste the key into this field." + }, + "premium": { + "message": "Premium", + "description": "Premium membership" + }, + "cardExpiredTitle": { + "message": "Expired card" + }, + "cardExpiredMessage": { + "message": "If you've renewed it, update the card's information" + }, "verificationRequired": { "message": "Verification required", "description": "Default title for the user verification dialog." @@ -2084,6 +2182,15 @@ "personalOwnershipPolicyInEffectImports": { "message": "An organisation policy has blocked importing items into your individual vault." }, + "personalDetails": { + "message": "Personal details" + }, + "identification": { + "message": "Identification" + }, + "contactInfo": { + "message": "Contact information" + }, "allSends": { "message": "All Sends", "description": "'Sends' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." @@ -2518,6 +2625,9 @@ "generateEmail": { "message": "Generate email" }, + "usernameGenerator": { + "message": "Username generator" + }, "spinboxBoundariesHint": { "message": "Value must be between $MIN$ and $MAX$.", "description": "Explains spin box minimum and maximum values to the user", @@ -3212,6 +3322,9 @@ "followTheStepsBelowToFinishLoggingIn": { "message": "Follow the steps below to finish logging in." }, + "followTheStepsBelowToFinishLoggingInWithSecurityKey": { + "message": "Follow the steps below to finish logging in with your security key." + }, "launchDuo": { "message": "Launch Duo in Browser" }, @@ -3436,6 +3549,17 @@ "ssoError": { "message": "No free ports could be found for the sso login." }, + "securePasswordGenerated": { + "message": "Secure password generated! Don't forget to also update your password on the website." + }, + "useGeneratorHelpTextPartOne": { + "message": "Use the generator", + "description": "This will be used as part of a larger sentence, broken up to include the generator icon. The full sentence will read 'Use the generator [GENERATOR_ICON] to create a strong unique password'" + }, + "useGeneratorHelpTextPartTwo": { + "message": "to create a strong unique password", + "description": "This will be used as part of a larger sentence, broken up to include the generator icon. The full sentence will read 'Use the generator [GENERATOR_ICON] to create a strong unique password'" + }, "biometricsStatusHelptextUnlockNeeded": { "message": "Biometric unlock is unavailable because PIN or password unlock is required first." }, @@ -3514,6 +3638,27 @@ "setupTwoStepLogin": { "message": "Set up two-step login" }, + "itemDetails": { + "message": "Item details" + }, + "itemName": { + "message": "Item name" + }, + "loginCredentials": { + "message": "Login credentials" + }, + "additionalOptions": { + "message": "Additional options" + }, + "itemHistory": { + "message": "Item history" + }, + "lastEdited": { + "message": "Last edited" + }, + "upload": { + "message": "Upload" + }, "newDeviceVerificationNoticeContentPage1": { "message": "Bitwarden will send a code to your account email to verify logins from new devices starting in February 2025." }, @@ -3567,5 +3712,35 @@ }, "move": { "message": "Move" + }, + "newLoginNudgeTitle": { + "message": "Save time with autofill" + }, + "newLoginNudgeBody": { + "message": "Include a Website so this login appears as an autofill suggestion." + }, + "newCardNudgeTitle": { + "message": "Seamless online checkout" + }, + "newCardNudgeBody": { + "message": "With cards, easily autofill payment forms securely and accurately." + }, + "newIdentityNudgeTitle": { + "message": "Simplify creating accounts" + }, + "newIdentityNudgeBody": { + "message": "With identities, quickly autofill long registration or contact forms." + }, + "newNoteNudgeTitle": { + "message": "Keep your sensitive data safe" + }, + "newNoteNudgeBody": { + "message": "With notes, securely store sensitive data like banking or insurance details." + }, + "newSshNudgeTitle": { + "message": "Developer-friendly SSH access" + }, + "newSshNudgeBody": { + "message": "Store your keys and connect with the SSH agent for fast, encrypted authentication." } } diff --git a/apps/desktop/src/locales/en_IN/messages.json b/apps/desktop/src/locales/en_IN/messages.json index 09003e8f0ab..8e40b110992 100644 --- a/apps/desktop/src/locales/en_IN/messages.json +++ b/apps/desktop/src/locales/en_IN/messages.json @@ -393,6 +393,64 @@ "authenticatorKeyTotp": { "message": "Authenticator key (TOTP)" }, + "authenticatorKey": { + "message": "Authenticator key" + }, + "autofillOptions": { + "message": "Autofill options" + }, + "websiteUri": { + "message": "Website (URI)" + }, + "websiteUriCount": { + "message": "Website (URI) $COUNT$", + "description": "Label for an input field that contains a website URI. The input field is part of a list of fields, and the count indicates the position of the field in the list.", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + } + } + }, + "websiteAdded": { + "message": "Website added" + }, + "addWebsite": { + "message": "Add website" + }, + "deleteWebsite": { + "message": "Delete website" + }, + "owner": { + "message": "Owner" + }, + "addField": { + "message": "Add field" + }, + "fieldType": { + "message": "Field type" + }, + "fieldLabel": { + "message": "Field label" + }, + "add": { + "message": "Add" + }, + "textHelpText": { + "message": "Use text fields for data like security questions" + }, + "hiddenHelpText": { + "message": "Use hidden fields for sensitive data like a password" + }, + "checkBoxHelpText": { + "message": "Use checkboxes if you'd like to autofill a form's checkbox, like a remember email" + }, + "linkedHelpText": { + "message": "Use a linked field when you are experiencing autofill issues for a specific website." + }, + "linkedLabelHelpText": { + "message": "Enter the field's html id, name, aria-label, or placeholder." + }, "folder": { "message": "Folder" }, @@ -418,6 +476,9 @@ "message": "Linked", "description": "This describes a field that is 'linked' (related) to another field." }, + "cfTypeCheckbox": { + "message": "Checkbox" + }, "linkedValue": { "message": "Linked value", "description": "This describes a value that is 'linked' (related) to another value." @@ -1915,6 +1976,43 @@ } } }, + "cardDetails": { + "message": "Card details" + }, + "cardBrandDetails": { + "message": "$BRAND$ details", + "placeholders": { + "brand": { + "content": "$1", + "example": "Visa" + } + } + }, + "learnMoreAboutAuthenticators": { + "message": "Learn more about authenticators" + }, + "copyTOTP": { + "message": "Copy Authenticator key (TOTP)" + }, + "totpHelperTitle": { + "message": "Make 2-step verification seamless" + }, + "totpHelper": { + "message": "Bitwarden can store and fill 2-step verification codes. Copy and paste the key into this field." + }, + "totpHelperWithCapture": { + "message": "Bitwarden can store and fill 2-step verification codes. Select the camera icon to take a screenshot of this website's authenticator QR code, or copy and paste the key into this field." + }, + "premium": { + "message": "Premium", + "description": "Premium membership" + }, + "cardExpiredTitle": { + "message": "Expired card" + }, + "cardExpiredMessage": { + "message": "If you've renewed it, update the card's information" + }, "verificationRequired": { "message": "Verification required", "description": "Default title for the user verification dialog." @@ -2084,6 +2182,15 @@ "personalOwnershipPolicyInEffectImports": { "message": "An organisation policy has blocked importing items into your individual vault." }, + "personalDetails": { + "message": "Personal details" + }, + "identification": { + "message": "Identification" + }, + "contactInfo": { + "message": "Contact information" + }, "allSends": { "message": "All Sends", "description": "'Sends' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." @@ -2518,6 +2625,9 @@ "generateEmail": { "message": "Generate email" }, + "usernameGenerator": { + "message": "Username generator" + }, "spinboxBoundariesHint": { "message": "Value must be between $MIN$ and $MAX$.", "description": "Explains spin box minimum and maximum values to the user", @@ -3212,6 +3322,9 @@ "followTheStepsBelowToFinishLoggingIn": { "message": "Follow the steps below to finish logging in." }, + "followTheStepsBelowToFinishLoggingInWithSecurityKey": { + "message": "Follow the steps below to finish logging in with your security key." + }, "launchDuo": { "message": "Launch Duo in Browser" }, @@ -3436,6 +3549,17 @@ "ssoError": { "message": "No free ports could be found for the sso login." }, + "securePasswordGenerated": { + "message": "Secure password generated! Don't forget to also update your password on the website." + }, + "useGeneratorHelpTextPartOne": { + "message": "Use the generator", + "description": "This will be used as part of a larger sentence, broken up to include the generator icon. The full sentence will read 'Use the generator [GENERATOR_ICON] to create a strong unique password'" + }, + "useGeneratorHelpTextPartTwo": { + "message": "to create a strong unique password", + "description": "This will be used as part of a larger sentence, broken up to include the generator icon. The full sentence will read 'Use the generator [GENERATOR_ICON] to create a strong unique password'" + }, "biometricsStatusHelptextUnlockNeeded": { "message": "Biometric unlock is unavailable because PIN or password unlock is required first." }, @@ -3514,6 +3638,27 @@ "setupTwoStepLogin": { "message": "Set up two-step login" }, + "itemDetails": { + "message": "Item details" + }, + "itemName": { + "message": "Item name" + }, + "loginCredentials": { + "message": "Login credentials" + }, + "additionalOptions": { + "message": "Additional options" + }, + "itemHistory": { + "message": "Item history" + }, + "lastEdited": { + "message": "Last edited" + }, + "upload": { + "message": "Upload" + }, "newDeviceVerificationNoticeContentPage1": { "message": "Bitwarden will send a code to your account email to verify logins from new devices starting in February 2025." }, @@ -3567,5 +3712,35 @@ }, "move": { "message": "Move" + }, + "newLoginNudgeTitle": { + "message": "Save time with autofill" + }, + "newLoginNudgeBody": { + "message": "Include a Website so this login appears as an autofill suggestion." + }, + "newCardNudgeTitle": { + "message": "Seamless online checkout" + }, + "newCardNudgeBody": { + "message": "With cards, easily autofill payment forms securely and accurately." + }, + "newIdentityNudgeTitle": { + "message": "Simplify creating accounts" + }, + "newIdentityNudgeBody": { + "message": "With identities, quickly autofill long registration or contact forms." + }, + "newNoteNudgeTitle": { + "message": "Keep your sensitive data safe" + }, + "newNoteNudgeBody": { + "message": "With notes, securely store sensitive data like banking or insurance details." + }, + "newSshNudgeTitle": { + "message": "Developer-friendly SSH access" + }, + "newSshNudgeBody": { + "message": "Store your keys and connect with the SSH agent for fast, encrypted authentication." } } diff --git a/apps/desktop/src/locales/eo/messages.json b/apps/desktop/src/locales/eo/messages.json index 8f9cda666f8..54322042979 100644 --- a/apps/desktop/src/locales/eo/messages.json +++ b/apps/desktop/src/locales/eo/messages.json @@ -393,6 +393,64 @@ "authenticatorKeyTotp": { "message": "Authenticator key (TOTP)" }, + "authenticatorKey": { + "message": "Authenticator key" + }, + "autofillOptions": { + "message": "Autofill options" + }, + "websiteUri": { + "message": "Website (URI)" + }, + "websiteUriCount": { + "message": "Website (URI) $COUNT$", + "description": "Label for an input field that contains a website URI. The input field is part of a list of fields, and the count indicates the position of the field in the list.", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + } + } + }, + "websiteAdded": { + "message": "Website added" + }, + "addWebsite": { + "message": "Add website" + }, + "deleteWebsite": { + "message": "Delete website" + }, + "owner": { + "message": "Owner" + }, + "addField": { + "message": "Add field" + }, + "fieldType": { + "message": "Field type" + }, + "fieldLabel": { + "message": "Field label" + }, + "add": { + "message": "Add" + }, + "textHelpText": { + "message": "Use text fields for data like security questions" + }, + "hiddenHelpText": { + "message": "Use hidden fields for sensitive data like a password" + }, + "checkBoxHelpText": { + "message": "Use checkboxes if you'd like to autofill a form's checkbox, like a remember email" + }, + "linkedHelpText": { + "message": "Use a linked field when you are experiencing autofill issues for a specific website." + }, + "linkedLabelHelpText": { + "message": "Enter the the field's html id, name, aria-label, or placeholder." + }, "folder": { "message": "Dosierujo" }, @@ -418,6 +476,9 @@ "message": "Ligita", "description": "This describes a field that is 'linked' (related) to another field." }, + "cfTypeCheckbox": { + "message": "Checkbox" + }, "linkedValue": { "message": "Ligita valoro", "description": "This describes a value that is 'linked' (related) to another value." @@ -1915,6 +1976,43 @@ } } }, + "cardDetails": { + "message": "Card details" + }, + "cardBrandDetails": { + "message": "$BRAND$ details", + "placeholders": { + "brand": { + "content": "$1", + "example": "Visa" + } + } + }, + "learnMoreAboutAuthenticators": { + "message": "Learn more about authenticators" + }, + "copyTOTP": { + "message": "Copy Authenticator key (TOTP)" + }, + "totpHelperTitle": { + "message": "Make 2-step verification seamless" + }, + "totpHelper": { + "message": "Bitwarden can store and fill 2-step verification codes. Copy and paste the key into this field." + }, + "totpHelperWithCapture": { + "message": "Bitwarden can store and fill 2-step verification codes. Select the camera icon to take a screenshot of this website's authenticator QR code, or copy and paste the key into this field." + }, + "premium": { + "message": "Premium", + "description": "Premium membership" + }, + "cardExpiredTitle": { + "message": "Expired card" + }, + "cardExpiredMessage": { + "message": "If you've renewed it, update the card's information" + }, "verificationRequired": { "message": "Verification required", "description": "Default title for the user verification dialog." @@ -2084,6 +2182,15 @@ "personalOwnershipPolicyInEffectImports": { "message": "An organization policy has blocked importing items into your individual vault." }, + "personalDetails": { + "message": "Personal details" + }, + "identification": { + "message": "Identification" + }, + "contactInfo": { + "message": "Contact information" + }, "allSends": { "message": "All Sends", "description": "'Sends' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." @@ -2518,6 +2625,9 @@ "generateEmail": { "message": "Generate email" }, + "usernameGenerator": { + "message": "Username generator" + }, "spinboxBoundariesHint": { "message": "Value must be between $MIN$ and $MAX$.", "description": "Explains spin box minimum and maximum values to the user", @@ -3212,6 +3322,9 @@ "followTheStepsBelowToFinishLoggingIn": { "message": "Follow the steps below to finish logging in." }, + "followTheStepsBelowToFinishLoggingInWithSecurityKey": { + "message": "Follow the steps below to finish logging in with your security key." + }, "launchDuo": { "message": "Launch Duo in Browser" }, @@ -3436,6 +3549,17 @@ "ssoError": { "message": "No free ports could be found for the sso login." }, + "securePasswordGenerated": { + "message": "Secure password generated! Don't forget to also update your password on the website." + }, + "useGeneratorHelpTextPartOne": { + "message": "Use the generator", + "description": "This will be used as part of a larger sentence, broken up to include the generator icon. The full sentence will read 'Use the generator [GENERATOR_ICON] to create a strong unique password'" + }, + "useGeneratorHelpTextPartTwo": { + "message": "to create a strong unique password", + "description": "This will be used as part of a larger sentence, broken up to include the generator icon. The full sentence will read 'Use the generator [GENERATOR_ICON] to create a strong unique password'" + }, "biometricsStatusHelptextUnlockNeeded": { "message": "Biometric unlock is unavailable because PIN or password unlock is required first." }, @@ -3514,6 +3638,27 @@ "setupTwoStepLogin": { "message": "Set up two-step login" }, + "itemDetails": { + "message": "Item details" + }, + "itemName": { + "message": "Item name" + }, + "loginCredentials": { + "message": "Login credentials" + }, + "additionalOptions": { + "message": "Additional options" + }, + "itemHistory": { + "message": "Item history" + }, + "lastEdited": { + "message": "Last edited" + }, + "upload": { + "message": "Upload" + }, "newDeviceVerificationNoticeContentPage1": { "message": "Bitwarden will send a code to your account email to verify logins from new devices starting in February 2025." }, @@ -3567,5 +3712,35 @@ }, "move": { "message": "Move" + }, + "newLoginNudgeTitle": { + "message": "Save time with autofill" + }, + "newLoginNudgeBody": { + "message": "Include a Website so this login appears as an autofill suggestion." + }, + "newCardNudgeTitle": { + "message": "Seamless online checkout" + }, + "newCardNudgeBody": { + "message": "With cards, easily autofill payment forms securely and accurately." + }, + "newIdentityNudgeTitle": { + "message": "Simplify creating accounts" + }, + "newIdentityNudgeBody": { + "message": "With identities, quickly autofill long registration or contact forms." + }, + "newNoteNudgeTitle": { + "message": "Keep your sensitive data safe" + }, + "newNoteNudgeBody": { + "message": "With notes, securely store sensitive data like banking or insurance details." + }, + "newSshNudgeTitle": { + "message": "Developer-friendly SSH access" + }, + "newSshNudgeBody": { + "message": "Store your keys and connect with the SSH agent for fast, encrypted authentication." } } diff --git a/apps/desktop/src/locales/es/messages.json b/apps/desktop/src/locales/es/messages.json index 3ddac01b47c..823d0cc6a2c 100644 --- a/apps/desktop/src/locales/es/messages.json +++ b/apps/desktop/src/locales/es/messages.json @@ -27,7 +27,7 @@ "message": "Nota segura" }, "typeSshKey": { - "message": "SSH key" + "message": "Clave SSH" }, "folders": { "message": "Carpetas" @@ -205,19 +205,19 @@ "message": "RSA 4096-Bit" }, "sshKeyGenerated": { - "message": "A new SSH key was generated" + "message": "Se generó una nueva clave SSH" }, "sshKeyWrongPassword": { "message": "La contraseña introducida es incorrecta." }, "importSshKey": { - "message": "Import" + "message": "Importar" }, "confirmSshKeyPassword": { "message": "Confirmar contraseña" }, "enterSshKeyPasswordDesc": { - "message": "Enter the password for the SSH key." + "message": "Introduce la contraseña para la clave SSH." }, "enterSshKeyPassword": { "message": "Introducir la contraseña" @@ -250,17 +250,17 @@ "message": "Error" }, "decryptionError": { - "message": "Decryption error" + "message": "Error de descifrado" }, "couldNotDecryptVaultItemsBelow": { - "message": "Bitwarden could not decrypt the vault item(s) listed below." + "message": "Bitwarden no pudo descifrar el/los elemento(s) de la caja fuerte listados a continuación." }, "contactCSToAvoidDataLossPart1": { "message": "Contact customer success", "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" }, "contactCSToAvoidDataLossPart2": { - "message": "to avoid additional data loss.", + "message": "para evitar pérdida de datos adicionales.", "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" }, "january": { @@ -393,6 +393,64 @@ "authenticatorKeyTotp": { "message": "Clave de autenticación (TOTP)" }, + "authenticatorKey": { + "message": "Authenticator key" + }, + "autofillOptions": { + "message": "Autofill options" + }, + "websiteUri": { + "message": "Website (URI)" + }, + "websiteUriCount": { + "message": "Website (URI) $COUNT$", + "description": "Label for an input field that contains a website URI. The input field is part of a list of fields, and the count indicates the position of the field in the list.", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + } + } + }, + "websiteAdded": { + "message": "Website added" + }, + "addWebsite": { + "message": "Add website" + }, + "deleteWebsite": { + "message": "Delete website" + }, + "owner": { + "message": "Owner" + }, + "addField": { + "message": "Add field" + }, + "fieldType": { + "message": "Field type" + }, + "fieldLabel": { + "message": "Field label" + }, + "add": { + "message": "Add" + }, + "textHelpText": { + "message": "Use text fields for data like security questions" + }, + "hiddenHelpText": { + "message": "Use hidden fields for sensitive data like a password" + }, + "checkBoxHelpText": { + "message": "Use checkboxes if you'd like to autofill a form's checkbox, like a remember email" + }, + "linkedHelpText": { + "message": "Use a linked field when you are experiencing autofill issues for a specific website." + }, + "linkedLabelHelpText": { + "message": "Enter the the field's html id, name, aria-label, or placeholder." + }, "folder": { "message": "Carpeta" }, @@ -418,6 +476,9 @@ "message": "Conectado", "description": "This describes a field that is 'linked' (related) to another field." }, + "cfTypeCheckbox": { + "message": "Checkbox" + }, "linkedValue": { "message": "Valor vinculado", "description": "This describes a value that is 'linked' (related) to another value." @@ -475,10 +536,10 @@ "message": "Copiar contraseña" }, "regenerateSshKey": { - "message": "Regenerate SSH key" + "message": "Regenerar clave SSH" }, "copySshPrivateKey": { - "message": "Copy SSH private key" + "message": "Copiar clave privada SSH" }, "copyPassphrase": { "message": "Copy passphrase", @@ -516,7 +577,7 @@ "description": "Card header for password generator include block" }, "uppercaseDescription": { - "message": "Include uppercase characters", + "message": "Incluir letras mayúsculas", "description": "Tooltip for the password generator uppercase character checkbox" }, "uppercaseLabel": { @@ -524,7 +585,7 @@ "description": "Label for the password generator uppercase character checkbox" }, "lowercaseDescription": { - "message": "Include lowercase characters", + "message": "Incluir letras minúsculas", "description": "Full description for the password generator lowercase character checkbox" }, "lowercaseLabel": { @@ -540,7 +601,7 @@ "description": "Label for the password generator numbers checkbox" }, "specialCharactersDescription": { - "message": "Include special characters", + "message": "Incluir caracteres especiales", "description": "Full description for the password generator special characters checkbox" }, "numWords": { @@ -571,7 +632,7 @@ "description": "deprecated. Use avoidAmbiguous instead." }, "avoidAmbiguous": { - "message": "Avoid ambiguous characters", + "message": "Evitar caracteres ambiguos", "description": "Label for the avoid ambiguous characters checkbox." }, "generatorPolicyInEffect": { @@ -646,16 +707,16 @@ "message": "Identificarse" }, "logInToBitwarden": { - "message": "Log in to Bitwarden" + "message": "Iniciar sesión en Bitwarden" }, "enterTheCodeSentToYourEmail": { - "message": "Enter the code sent to your email" + "message": "Introduce el código enviado a tu correo electrónico" }, "enterTheCodeFromYourAuthenticatorApp": { - "message": "Enter the code from your authenticator app" + "message": "Introduce el código de tu aplicación de autenticación" }, "pressYourYubiKeyToAuthenticate": { - "message": "Press your YubiKey to authenticate" + "message": "Pulsa tu YubiKey para autenticarte" }, "logInWithPasskey": { "message": "Log in with passkey" @@ -722,7 +783,7 @@ "message": "Unirse a organización" }, "joinOrganizationName": { - "message": "Join $ORGANIZATIONNAME$", + "message": "Unirse a $ORGANIZATIONNAME$", "placeholders": { "organizationName": { "content": "$1", @@ -825,7 +886,7 @@ "message": "La autenticación fue cancelada o tardó demasiado. Por favor, inténtalo de nuevo." }, "openInNewTab": { - "message": "Open in new tab" + "message": "Abrir en nueva pestaña" }, "invalidVerificationCode": { "message": "Código de verificación incorrecto" @@ -843,7 +904,7 @@ } }, "dontAskAgainOnThisDeviceFor30Days": { - "message": "Don't ask again on this device for 30 days" + "message": "No volver a preguntar en este dispositivo durante 30 días" }, "selectAnotherMethod": { "message": "Select another method", @@ -883,10 +944,10 @@ "description": "'Duo Security' and 'Duo Mobile' are product names and should not be translated." }, "verifyYourIdentity": { - "message": "Verify your Identity" + "message": "Verifica tu Identidad" }, "weDontRecognizeThisDevice": { - "message": "We don't recognize this device. Enter the code sent to your email to verify your identity." + "message": "No reconocemos este dispositivo. Introduce el código enviado a tu correo electrónico para verificar tu identidad." }, "continueLoggingIn": { "message": "Continue logging in" @@ -974,7 +1035,7 @@ "message": "No" }, "location": { - "message": "Location" + "message": "Ubicación" }, "overwritePassword": { "message": "Sobreescribir contraseña" @@ -1093,7 +1154,7 @@ "message": "Tu caja fuerte está bloqueada. Verifica tu contraseña maestra para continuar." }, "yourAccountIsLocked": { - "message": "Your account is locked" + "message": "Tu cuenta está bloqueada" }, "or": { "message": "o" @@ -1340,7 +1401,7 @@ "description": "Copy credit card number" }, "copyEmail": { - "message": "Copy email" + "message": "Copiar correo electrónico" }, "copySecurityCode": { "message": "Copiar código de seguridad", @@ -1426,13 +1487,13 @@ "message": "No hay contraseñas que listar." }, "clearHistory": { - "message": "Clear history" + "message": "Limpiar historial" }, "nothingToShow": { - "message": "Nothing to show" + "message": "Nada que mostrar" }, "nothingGeneratedRecently": { - "message": "You haven't generated anything recently" + "message": "No has generado nada recientemente" }, "undo": { "message": "Deshacer" @@ -1773,7 +1834,7 @@ "message": "Solicitar contraseña o PIN al iniciar la aplicación" }, "requirePasswordWithoutPinOnStart": { - "message": "Require password on app start" + "message": "Solicitar contraseña al iniciar la aplicación" }, "recommendedForSecurity": { "message": "Recomendado por seguridad." @@ -1791,7 +1852,7 @@ "message": "El borrado de su cuenta es una operación permanente. No se puede deshacer." }, "cannotDeleteAccount": { - "message": "Cannot delete account" + "message": "No se puede eliminar la cuenta" }, "cannotDeleteAccountDesc": { "message": "This action cannot be completed because your account is owned by an organization. Contact your organization administrator for additional details." @@ -1907,7 +1968,7 @@ "description": "Used as a card title description on the set password page to explain why the user is there" }, "cardMetrics": { - "message": "out of $TOTAL$", + "message": "de $TOTAL$", "placeholders": { "total": { "content": "$1", @@ -1915,6 +1976,43 @@ } } }, + "cardDetails": { + "message": "Card details" + }, + "cardBrandDetails": { + "message": "$BRAND$ details", + "placeholders": { + "brand": { + "content": "$1", + "example": "Visa" + } + } + }, + "learnMoreAboutAuthenticators": { + "message": "Learn more about authenticators" + }, + "copyTOTP": { + "message": "Copy Authenticator key (TOTP)" + }, + "totpHelperTitle": { + "message": "Make 2-step verification seamless" + }, + "totpHelper": { + "message": "Bitwarden can store and fill 2-step verification codes. Copy and paste the key into this field." + }, + "totpHelperWithCapture": { + "message": "Bitwarden can store and fill 2-step verification codes. Select the camera icon to take a screenshot of this website's authenticator QR code, or copy and paste the key into this field." + }, + "premium": { + "message": "Premium", + "description": "Premium membership" + }, + "cardExpiredTitle": { + "message": "Expired card" + }, + "cardExpiredMessage": { + "message": "If you've renewed it, update the card's information" + }, "verificationRequired": { "message": "Verificación requerida", "description": "Default title for the user verification dialog." @@ -2073,7 +2171,7 @@ "message": "Debido a una política de organización, tiene restringido el guardar elementos a su caja fuerte personal. Cambie la configuración de propietario a organización y elija entre las colecciones disponibles." }, "yourNewPasswordCannotBeTheSameAsYourCurrentPassword": { - "message": "Your new password cannot be the same as your current password." + "message": "Tu nueva contraseña no puede ser la misma que tu contraseña actual." }, "hintEqualsPassword": { "message": "La pista para la contraseña no puede ser igual que la contraseña." @@ -2084,6 +2182,15 @@ "personalOwnershipPolicyInEffectImports": { "message": "Una política organizacional ha bloqueado la importación de elementos a su caja fuerte personal." }, + "personalDetails": { + "message": "Personal details" + }, + "identification": { + "message": "Identification" + }, + "contactInfo": { + "message": "Contact information" + }, "allSends": { "message": "Todos los Send", "description": "'Sends' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." @@ -2248,7 +2355,7 @@ "message": "Autenticar WebAuthn" }, "readSecurityKey": { - "message": "Read security key" + "message": "Leer clave de seguridad" }, "awaitingSecurityKeyInteraction": { "message": "Awaiting security key interaction..." @@ -2494,7 +2601,7 @@ "message": "Bloqueado" }, "yourVaultIsLockedV2": { - "message": "Your vault is locked" + "message": "Tu caja fuerte está bloqueada" }, "unlocked": { "message": "Desbloqueado" @@ -2516,7 +2623,10 @@ "message": "Generar nombre de usuario" }, "generateEmail": { - "message": "Generate email" + "message": "Generar correo electrónico" + }, + "usernameGenerator": { + "message": "Username generator" }, "spinboxBoundariesHint": { "message": "Value must be between $MIN$ and $MAX$.", @@ -2569,7 +2679,7 @@ "message": "Utiliza la bandeja de entrada Catch-All configurada por tu dominio." }, "useThisEmail": { - "message": "Use this email" + "message": "Usar este correo electrónico" }, "random": { "message": "Aleatorio" @@ -2782,7 +2892,7 @@ "message": "Inicio de sesión iniciado" }, "logInRequestSent": { - "message": "Request sent" + "message": "Solicitud enviada" }, "notificationSentDevice": { "message": "Se ha enviado una notificación a tu dispositivo." @@ -2800,7 +2910,7 @@ "message": "Make sure the Fingerprint phrase matches the one below before approving." }, "needAnotherOptionV1": { - "message": "Need another option?" + "message": "¿Necesitas otra opción?" }, "fingerprintMatchInfo": { "message": "Por favor, asegúrese de que su caja fuerte está desbloqueada y la frase de huella dactilar coincide con el otro dispositivo." @@ -2809,7 +2919,7 @@ "message": "Frase de huella dactilar" }, "youWillBeNotifiedOnceTheRequestIsApproved": { - "message": "You will be notified once the request is approved" + "message": "Serás notificado una vez que la solicitud sea aprobada" }, "needAnotherOption": { "message": "Iniciar sesión con el dispositivo debe estar habilitado en los ajustes de la aplicación móvil Bitwarden. ¿Necesitas otra opción?" @@ -2849,10 +2959,10 @@ "message": "Hora" }, "confirmAccess": { - "message": "Confirm access" + "message": "Confirmar acceso" }, "denyAccess": { - "message": "Deny access" + "message": "Denegar acceso" }, "logInConfirmedForEmailOnDevice": { "message": "Inicio de sesión confirmado para $EMAIL$ en $DEVICE$", @@ -2889,7 +2999,7 @@ "message": "Esta solicitud ya no es válida." }, "confirmAccessAttempt": { - "message": "Confirm access attempt for $EMAIL$", + "message": "Confirmar intento de acceso para $EMAIL$", "placeholders": { "email": { "content": "$1", @@ -2937,10 +3047,10 @@ "message": "Contraseña débil encontrada en una filtración de datos. Utilice una contraseña única para proteger su cuenta. ¿Está seguro de que desea utilizar una contraseña comprometida?" }, "useThisPassword": { - "message": "Use this password" + "message": "Usar esta contraseña" }, "useThisUsername": { - "message": "Use this username" + "message": "Usar este nombre de usuario" }, "checkForBreaches": { "message": "Comprobar filtración de datos conocidos para esta contraseña" @@ -2952,7 +3062,7 @@ "message": "Importante:" }, "accessing": { - "message": "Accessing" + "message": "Accediendo" }, "accessTokenUnableToBeDecrypted": { "message": "Se ha cerrado la sesión porque tu token de acceso no pudo ser descifrado. Por favor, inicia sesión de nuevo para resolver este problema." @@ -2985,10 +3095,10 @@ "message": "Se requiere aprobación del dispositivo. Selecciona una opción de aprobación a continuación:" }, "deviceApprovalRequiredV2": { - "message": "Device approval required" + "message": "Se requiere la aprobación del dispositivo" }, "selectAnApprovalOptionBelow": { - "message": "Select an approval option below" + "message": "Selecciona una opción de aprobación abajo" }, "rememberThisDevice": { "message": "Recordar este dispositivo" @@ -3212,6 +3322,9 @@ "followTheStepsBelowToFinishLoggingIn": { "message": "Follow the steps below to finish logging in." }, + "followTheStepsBelowToFinishLoggingInWithSecurityKey": { + "message": "Follow the steps below to finish logging in with your security key." + }, "launchDuo": { "message": "Inicia Duo en el navegador" }, @@ -3436,6 +3549,17 @@ "ssoError": { "message": "No free ports could be found for the sso login." }, + "securePasswordGenerated": { + "message": "Secure password generated! Don't forget to also update your password on the website." + }, + "useGeneratorHelpTextPartOne": { + "message": "Use the generator", + "description": "This will be used as part of a larger sentence, broken up to include the generator icon. The full sentence will read 'Use the generator [GENERATOR_ICON] to create a strong unique password'" + }, + "useGeneratorHelpTextPartTwo": { + "message": "to create a strong unique password", + "description": "This will be used as part of a larger sentence, broken up to include the generator icon. The full sentence will read 'Use the generator [GENERATOR_ICON] to create a strong unique password'" + }, "biometricsStatusHelptextUnlockNeeded": { "message": "Biometric unlock is unavailable because PIN or password unlock is required first." }, @@ -3461,13 +3585,13 @@ "message": "Biometric unlock is currently unavailable for an unknown reason." }, "authorize": { - "message": "Authorize" + "message": "Autorizar" }, "deny": { - "message": "Deny" + "message": "Denegar" }, "sshkeyApprovalTitle": { - "message": "Confirm SSH key usage" + "message": "Confirmar uso de clave SSH" }, "agentForwardingWarningTitle": { "message": "Warning: Agent Forwarding" @@ -3476,7 +3600,7 @@ "message": "This request comes from a remote device that you are logged into" }, "sshkeyApprovalMessageInfix": { - "message": "is requesting access to" + "message": "está solicitando acceso a" }, "sshkeyApprovalMessageSuffix": { "message": "in order to" @@ -3491,29 +3615,50 @@ "message": "sign a git commit" }, "unknownApplication": { - "message": "An application" + "message": "Una aplicación" }, "invalidSshKey": { - "message": "The SSH key is invalid" + "message": "La clave SSH no es válida" }, "sshKeyTypeUnsupported": { - "message": "The SSH key type is not supported" + "message": "El tipo de clave SSH no está soportado" }, "importSshKeyFromClipboard": { - "message": "Import key from clipboard" + "message": "Importar clave del portapapeles" }, "sshKeyImported": { - "message": "SSH key imported successfully" + "message": "Clave SSH importada correctamente" }, "fileSavedToDevice": { "message": "File saved to device. Manage from your device downloads." }, "importantNotice": { - "message": "Important notice" + "message": "Aviso importante" }, "setupTwoStepLogin": { "message": "Set up two-step login" }, + "itemDetails": { + "message": "Item details" + }, + "itemName": { + "message": "Item name" + }, + "loginCredentials": { + "message": "Login credentials" + }, + "additionalOptions": { + "message": "Additional options" + }, + "itemHistory": { + "message": "Item history" + }, + "lastEdited": { + "message": "Last edited" + }, + "upload": { + "message": "Upload" + }, "newDeviceVerificationNoticeContentPage1": { "message": "Bitwarden will send a code to your account email to verify logins from new devices starting in February 2025." }, @@ -3521,10 +3666,10 @@ "message": "You can set up two-step login as an alternative way to protect your account or change your email to one you can access." }, "remindMeLater": { - "message": "Remind me later" + "message": "Recuérdame más tarde" }, "newDeviceVerificationNoticePageOneFormContent": { - "message": "Do you have reliable access to your email, $EMAIL$?", + "message": "¿Tienes acceso fiable a tu correo electrónico, $EMAIL$?", "placeholders": { "email": { "content": "$1", @@ -3533,13 +3678,13 @@ } }, "newDeviceVerificationNoticePageOneEmailAccessNo": { - "message": "No, I do not" + "message": "No, no lo tengo" }, "newDeviceVerificationNoticePageOneEmailAccessYes": { - "message": "Yes, I can reliably access my email" + "message": "Sí, puedo acceder a mi correo electrónico de forma fiable" }, "turnOnTwoStepLogin": { - "message": "Turn on two-step login" + "message": "Activar inicio de sesión en dos pasos" }, "changeAcctEmail": { "message": "Change account email" @@ -3554,10 +3699,10 @@ "message": "Confirm window still visible" }, "confirmWindowStillVisibleContent": { - "message": "Please confirm that the window is still visible." + "message": "Por favor, confirma que la ventana sigue siendo visible." }, "updateBrowserOrDisableFingerprintDialogTitle": { - "message": "Extension update required" + "message": "Actualización de la extensión requerida" }, "updateBrowserOrDisableFingerprintDialogMessage": { "message": "The browser extension you are using is out of date. Please update it or disable browser integration fingerprint validation in the desktop app settings." @@ -3566,6 +3711,36 @@ "message": "Change at-risk password" }, "move": { - "message": "Move" + "message": "Mover" + }, + "newLoginNudgeTitle": { + "message": "Save time with autofill" + }, + "newLoginNudgeBody": { + "message": "Include a Website so this login appears as an autofill suggestion." + }, + "newCardNudgeTitle": { + "message": "Seamless online checkout" + }, + "newCardNudgeBody": { + "message": "With cards, easily autofill payment forms securely and accurately." + }, + "newIdentityNudgeTitle": { + "message": "Simplify creating accounts" + }, + "newIdentityNudgeBody": { + "message": "With identities, quickly autofill long registration or contact forms." + }, + "newNoteNudgeTitle": { + "message": "Keep your sensitive data safe" + }, + "newNoteNudgeBody": { + "message": "With notes, securely store sensitive data like banking or insurance details." + }, + "newSshNudgeTitle": { + "message": "Developer-friendly SSH access" + }, + "newSshNudgeBody": { + "message": "Store your keys and connect with the SSH agent for fast, encrypted authentication." } } diff --git a/apps/desktop/src/locales/et/messages.json b/apps/desktop/src/locales/et/messages.json index cbec15daf53..e2fb62c4df2 100644 --- a/apps/desktop/src/locales/et/messages.json +++ b/apps/desktop/src/locales/et/messages.json @@ -223,10 +223,10 @@ "message": "Sisesta salasõna" }, "sshAgentUnlockRequired": { - "message": "Please unlock your vault to approve the SSH key request." + "message": "Palun ava oma hoidla, et kinnitada SSH võtme taotlus." }, "sshAgentUnlockTimeout": { - "message": "SSH key request timed out." + "message": "SSH võtme taotlus aegus." }, "enableSshAgent": { "message": "Enable SSH agent" @@ -250,17 +250,17 @@ "message": "Viga" }, "decryptionError": { - "message": "Decryption error" + "message": "Viga dekrüpteerimisel" }, "couldNotDecryptVaultItemsBelow": { - "message": "Bitwarden could not decrypt the vault item(s) listed below." + "message": "Bitwarden ei suutnud dekrüpteerida allpool olevaid hoidla kirjeid." }, "contactCSToAvoidDataLossPart1": { - "message": "Contact customer success", + "message": "Võta klienditoega ühendust,", "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" }, "contactCSToAvoidDataLossPart2": { - "message": "to avoid additional data loss.", + "message": "et vältida veel teiste andmete kaotust.", "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" }, "january": { @@ -393,6 +393,64 @@ "authenticatorKeyTotp": { "message": "Autentimise võti (TOTP)" }, + "authenticatorKey": { + "message": "Authenticator key" + }, + "autofillOptions": { + "message": "Autofill options" + }, + "websiteUri": { + "message": "Website (URI)" + }, + "websiteUriCount": { + "message": "Website (URI) $COUNT$", + "description": "Label for an input field that contains a website URI. The input field is part of a list of fields, and the count indicates the position of the field in the list.", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + } + } + }, + "websiteAdded": { + "message": "Website added" + }, + "addWebsite": { + "message": "Add website" + }, + "deleteWebsite": { + "message": "Delete website" + }, + "owner": { + "message": "Owner" + }, + "addField": { + "message": "Add field" + }, + "fieldType": { + "message": "Field type" + }, + "fieldLabel": { + "message": "Field label" + }, + "add": { + "message": "Add" + }, + "textHelpText": { + "message": "Use text fields for data like security questions" + }, + "hiddenHelpText": { + "message": "Use hidden fields for sensitive data like a password" + }, + "checkBoxHelpText": { + "message": "Use checkboxes if you'd like to autofill a form's checkbox, like a remember email" + }, + "linkedHelpText": { + "message": "Use a linked field when you are experiencing autofill issues for a specific website." + }, + "linkedLabelHelpText": { + "message": "Enter the the field's html id, name, aria-label, or placeholder." + }, "folder": { "message": "Kaust" }, @@ -418,6 +476,9 @@ "message": "Ühenduses", "description": "This describes a field that is 'linked' (related) to another field." }, + "cfTypeCheckbox": { + "message": "Checkbox" + }, "linkedValue": { "message": "Ühendatud väärtus", "description": "This describes a value that is 'linked' (related) to another value." @@ -475,10 +536,10 @@ "message": "Kopeeri parool" }, "regenerateSshKey": { - "message": "Regenerate SSH key" + "message": "Genereeri SSH võti uuesti" }, "copySshPrivateKey": { - "message": "Copy SSH private key" + "message": "Kopeeri SSH privaatne võti" }, "copyPassphrase": { "message": "Copy passphrase", @@ -516,23 +577,23 @@ "description": "Card header for password generator include block" }, "uppercaseDescription": { - "message": "Include uppercase characters", + "message": "Kasuta trükitähti", "description": "Tooltip for the password generator uppercase character checkbox" }, "uppercaseLabel": { - "message": "A-Z", + "message": "A-Y", "description": "Label for the password generator uppercase character checkbox" }, "lowercaseDescription": { - "message": "Include lowercase characters", + "message": "Kasuta kirjatähti", "description": "Full description for the password generator lowercase character checkbox" }, "lowercaseLabel": { - "message": "a-z", + "message": "a-y", "description": "Label for the password generator lowercase character checkbox" }, "numbersDescription": { - "message": "Include numbers", + "message": "Kasuta numbreid", "description": "Full description for the password generator numbers checkbox" }, "numbersLabel": { @@ -540,7 +601,7 @@ "description": "Label for the password generator numbers checkbox" }, "specialCharactersDescription": { - "message": "Include special characters", + "message": "Kasuta sümboleid", "description": "Full description for the password generator special characters checkbox" }, "numWords": { @@ -571,7 +632,7 @@ "description": "deprecated. Use avoidAmbiguous instead." }, "avoidAmbiguous": { - "message": "Avoid ambiguous characters", + "message": "Väldi raskesti eristatavaid tähti ja sümboleid", "description": "Label for the avoid ambiguous characters checkbox." }, "generatorPolicyInEffect": { @@ -634,7 +695,7 @@ "message": "Konto loomine" }, "newToBitwarden": { - "message": "New to Bitwarden?" + "message": "Esimest korda siin?" }, "setAStrongPassword": { "message": "Määra tugev parool" @@ -646,22 +707,22 @@ "message": "Logi sisse" }, "logInToBitwarden": { - "message": "Log in to Bitwarden" + "message": "Logi sisse Bitwardenisse" }, "enterTheCodeSentToYourEmail": { - "message": "Enter the code sent to your email" + "message": "Sisesta oma emailile saadetud kood" }, "enterTheCodeFromYourAuthenticatorApp": { - "message": "Enter the code from your authenticator app" + "message": "Sisesta kood oma autentimisrakendusest" }, "pressYourYubiKeyToAuthenticate": { - "message": "Press your YubiKey to authenticate" + "message": "Autentimiseks vajuta nuppu oma YubiKey'l" }, "logInWithPasskey": { - "message": "Log in with passkey" + "message": "Logi sisse pääsuvõtmega" }, "loginWithDevice": { - "message": "Log in with device" + "message": "Logi sisse teise seadmega" }, "useSingleSignOn": { "message": "Use single sign-on" @@ -710,7 +771,7 @@ "message": "Ülemparooli vihje" }, "passwordStrengthScore": { - "message": "Password strength score $SCORE$", + "message": "Parooli tugevuse aste: $SCORE$", "placeholders": { "score": { "content": "$1", @@ -722,7 +783,7 @@ "message": "Liitu organisatsiooniga" }, "joinOrganizationName": { - "message": "Join $ORGANIZATIONNAME$", + "message": "Liitu $ORGANIZATIONNAME$ organisatsiooniga", "placeholders": { "organizationName": { "content": "$1", @@ -825,7 +886,7 @@ "message": "Autentimine tühistati või kestis liiga kaua aega. Palun proovi uuesti." }, "openInNewTab": { - "message": "Open in new tab" + "message": "Ava uuel vahelehel" }, "invalidVerificationCode": { "message": "Vale kinnituskood" @@ -843,14 +904,14 @@ } }, "dontAskAgainOnThisDeviceFor30Days": { - "message": "Don't ask again on this device for 30 days" + "message": "Ära küsi 30 päeva jooksul uuesti" }, "selectAnotherMethod": { - "message": "Select another method", + "message": "Vali teine meetod", "description": "Select another two-step login method" }, "useYourRecoveryCode": { - "message": "Use your recovery code" + "message": "Kasuta taastamise koodi" }, "insertU2f": { "message": "Sisesta oma turvaline võti arvuti USB porti. Kui sellel on nupp, siis vajuta seda." @@ -883,13 +944,13 @@ "description": "'Duo Security' and 'Duo Mobile' are product names and should not be translated." }, "verifyYourIdentity": { - "message": "Verify your Identity" + "message": "Kinnitage oma Identiteet" }, "weDontRecognizeThisDevice": { - "message": "We don't recognize this device. Enter the code sent to your email to verify your identity." + "message": "Me ei tunne seda seadet. Enda identiteedi kinnitamiseks palun sisesta oma emailile saadetud kood." }, "continueLoggingIn": { - "message": "Continue logging in" + "message": "Jätka sisse logimist" }, "webAuthnTitle": { "message": "FIDO2 WebAuthn" @@ -916,7 +977,7 @@ "message": "Kaheastmelise sisselogimise valikud" }, "selectTwoStepLoginMethod": { - "message": "Select two-step login method" + "message": "Vali kaheastmelise sisselogimise meetod" }, "selfHostedEnvironment": { "message": "Self-hosted Environment" @@ -937,10 +998,10 @@ "message": "Serveri URL" }, "authenticationTimeout": { - "message": "Authentication timeout" + "message": "Sisselogimise ajalõpp" }, "authenticationSessionTimedOut": { - "message": "The authentication session timed out. Please restart the login process." + "message": "Sisselogimise sessioon on aegunud. Palun alusta sisse logimist uuesti." }, "selfHostBaseUrl": { "message": "Self-host server URL", @@ -974,7 +1035,7 @@ "message": "Ei" }, "location": { - "message": "Location" + "message": "Asukoht" }, "overwritePassword": { "message": "Kirjuta parool üle" @@ -1093,16 +1154,16 @@ "message": "Hoidla on lukus. Jätkamiseks sisesta ülemparool." }, "yourAccountIsLocked": { - "message": "Your account is locked" + "message": "Sinu konto on lukustatud" }, "or": { - "message": "or" + "message": "või" }, "unlockWithBiometrics": { - "message": "Unlock with biometrics" + "message": "Ava biomeetriaga" }, "unlockWithMasterPassword": { - "message": "Unlock with master password" + "message": "Ava ülemparooliga" }, "unlock": { "message": "Lukusta lahti" @@ -1410,13 +1471,13 @@ "message": "Paroolide ajalugu" }, "generatorHistory": { - "message": "Generator history" + "message": "Genereerija ajalugu" }, "clearGeneratorHistoryTitle": { - "message": "Clear generator history" + "message": "Tühjenda genereerija ajalugu" }, "cleargGeneratorHistoryDescription": { - "message": "If you continue, all entries will be permanently deleted from generator's history. Are you sure you want to continue?" + "message": "Jätkates kustutatakse kõik kirjed genereerija ajaloost. Kas sa oled kindel, et soovid jätkata?" }, "clear": { "message": "Tühjenda", @@ -1426,13 +1487,13 @@ "message": "Puuduvad paroolid, mida kuvada." }, "clearHistory": { - "message": "Clear history" + "message": "Tühjenda ajalugu" }, "nothingToShow": { - "message": "Nothing to show" + "message": "Siin ei ole midagi näidata" }, "nothingGeneratedRecently": { - "message": "You haven't generated anything recently" + "message": "Sa ei ole midagi hiljuti genereerinud" }, "undo": { "message": "Võta tagasi" @@ -1773,13 +1834,13 @@ "message": "Nõua parooli või PINi rakenduse kävitumisel" }, "requirePasswordWithoutPinOnStart": { - "message": "Require password on app start" + "message": "Nõua parooli rakenduse käivitamisel" }, "recommendedForSecurity": { "message": "Soovitatud turvalisuse huvides." }, "lockWithMasterPassOnRestart1": { - "message": "Lock with master password on restart" + "message": "Lukusta ülemparooliga, kui rakendus taaskäivitatakse" }, "deleteAccount": { "message": "Kustuta konto" @@ -1791,10 +1852,10 @@ "message": "Konto kustutamine on ühekordne tegevus. Seda ei saa tagasi võtta." }, "cannotDeleteAccount": { - "message": "Cannot delete account" + "message": "Ei õnnestunud kontot kustutada" }, "cannotDeleteAccountDesc": { - "message": "This action cannot be completed because your account is owned by an organization. Contact your organization administrator for additional details." + "message": "Seda käsku ei õnnestunud täita, sest sinu konto kuulub organisatsioonile. Lisadetailide jaoks võta ühendust oma organisatsiooni administraatoriga." }, "accountDeleted": { "message": "Konto on kustutatud" @@ -1915,6 +1976,43 @@ } } }, + "cardDetails": { + "message": "Card details" + }, + "cardBrandDetails": { + "message": "$BRAND$ details", + "placeholders": { + "brand": { + "content": "$1", + "example": "Visa" + } + } + }, + "learnMoreAboutAuthenticators": { + "message": "Learn more about authenticators" + }, + "copyTOTP": { + "message": "Copy Authenticator key (TOTP)" + }, + "totpHelperTitle": { + "message": "Make 2-step verification seamless" + }, + "totpHelper": { + "message": "Bitwarden can store and fill 2-step verification codes. Copy and paste the key into this field." + }, + "totpHelperWithCapture": { + "message": "Bitwarden can store and fill 2-step verification codes. Select the camera icon to take a screenshot of this website's authenticator QR code, or copy and paste the key into this field." + }, + "premium": { + "message": "Premium", + "description": "Premium membership" + }, + "cardExpiredTitle": { + "message": "Expired card" + }, + "cardExpiredMessage": { + "message": "If you've renewed it, update the card's information" + }, "verificationRequired": { "message": "Tuvastamine vajalik", "description": "Default title for the user verification dialog." @@ -2084,6 +2182,15 @@ "personalOwnershipPolicyInEffectImports": { "message": "Kirjete importimine isiklikku hoidlasse on organisatsiooni poolt keelatud." }, + "personalDetails": { + "message": "Personal details" + }, + "identification": { + "message": "Identification" + }, + "contactInfo": { + "message": "Contact information" + }, "allSends": { "message": "Kõik Sendid", "description": "'Sends' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." @@ -2518,6 +2625,9 @@ "generateEmail": { "message": "Generate email" }, + "usernameGenerator": { + "message": "Username generator" + }, "spinboxBoundariesHint": { "message": "Value must be between $MIN$ and $MAX$.", "description": "Explains spin box minimum and maximum values to the user", @@ -3212,6 +3322,9 @@ "followTheStepsBelowToFinishLoggingIn": { "message": "Follow the steps below to finish logging in." }, + "followTheStepsBelowToFinishLoggingInWithSecurityKey": { + "message": "Follow the steps below to finish logging in with your security key." + }, "launchDuo": { "message": "Käivita Duo brauseris" }, @@ -3436,6 +3549,17 @@ "ssoError": { "message": "SSO-ga sisselogimiseks ei leitud ühtegi vaba porti." }, + "securePasswordGenerated": { + "message": "Secure password generated! Don't forget to also update your password on the website." + }, + "useGeneratorHelpTextPartOne": { + "message": "Use the generator", + "description": "This will be used as part of a larger sentence, broken up to include the generator icon. The full sentence will read 'Use the generator [GENERATOR_ICON] to create a strong unique password'" + }, + "useGeneratorHelpTextPartTwo": { + "message": "to create a strong unique password", + "description": "This will be used as part of a larger sentence, broken up to include the generator icon. The full sentence will read 'Use the generator [GENERATOR_ICON] to create a strong unique password'" + }, "biometricsStatusHelptextUnlockNeeded": { "message": "Biometric unlock is unavailable because PIN or password unlock is required first." }, @@ -3514,6 +3638,27 @@ "setupTwoStepLogin": { "message": "Set up two-step login" }, + "itemDetails": { + "message": "Item details" + }, + "itemName": { + "message": "Item name" + }, + "loginCredentials": { + "message": "Login credentials" + }, + "additionalOptions": { + "message": "Additional options" + }, + "itemHistory": { + "message": "Item history" + }, + "lastEdited": { + "message": "Last edited" + }, + "upload": { + "message": "Upload" + }, "newDeviceVerificationNoticeContentPage1": { "message": "Bitwarden will send a code to your account email to verify logins from new devices starting in February 2025." }, @@ -3567,5 +3712,35 @@ }, "move": { "message": "Move" + }, + "newLoginNudgeTitle": { + "message": "Save time with autofill" + }, + "newLoginNudgeBody": { + "message": "Include a Website so this login appears as an autofill suggestion." + }, + "newCardNudgeTitle": { + "message": "Seamless online checkout" + }, + "newCardNudgeBody": { + "message": "With cards, easily autofill payment forms securely and accurately." + }, + "newIdentityNudgeTitle": { + "message": "Simplify creating accounts" + }, + "newIdentityNudgeBody": { + "message": "With identities, quickly autofill long registration or contact forms." + }, + "newNoteNudgeTitle": { + "message": "Keep your sensitive data safe" + }, + "newNoteNudgeBody": { + "message": "With notes, securely store sensitive data like banking or insurance details." + }, + "newSshNudgeTitle": { + "message": "Developer-friendly SSH access" + }, + "newSshNudgeBody": { + "message": "Store your keys and connect with the SSH agent for fast, encrypted authentication." } } diff --git a/apps/desktop/src/locales/eu/messages.json b/apps/desktop/src/locales/eu/messages.json index 6f13b2f3bc4..cfab30889eb 100644 --- a/apps/desktop/src/locales/eu/messages.json +++ b/apps/desktop/src/locales/eu/messages.json @@ -393,6 +393,64 @@ "authenticatorKeyTotp": { "message": "Autentifikazio-gakoa (TOTP)" }, + "authenticatorKey": { + "message": "Authenticator key" + }, + "autofillOptions": { + "message": "Autofill options" + }, + "websiteUri": { + "message": "Website (URI)" + }, + "websiteUriCount": { + "message": "Website (URI) $COUNT$", + "description": "Label for an input field that contains a website URI. The input field is part of a list of fields, and the count indicates the position of the field in the list.", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + } + } + }, + "websiteAdded": { + "message": "Website added" + }, + "addWebsite": { + "message": "Add website" + }, + "deleteWebsite": { + "message": "Delete website" + }, + "owner": { + "message": "Owner" + }, + "addField": { + "message": "Add field" + }, + "fieldType": { + "message": "Field type" + }, + "fieldLabel": { + "message": "Field label" + }, + "add": { + "message": "Add" + }, + "textHelpText": { + "message": "Use text fields for data like security questions" + }, + "hiddenHelpText": { + "message": "Use hidden fields for sensitive data like a password" + }, + "checkBoxHelpText": { + "message": "Use checkboxes if you'd like to autofill a form's checkbox, like a remember email" + }, + "linkedHelpText": { + "message": "Use a linked field when you are experiencing autofill issues for a specific website." + }, + "linkedLabelHelpText": { + "message": "Enter the the field's html id, name, aria-label, or placeholder." + }, "folder": { "message": "Karpeta" }, @@ -418,6 +476,9 @@ "message": "Konektatuta", "description": "This describes a field that is 'linked' (related) to another field." }, + "cfTypeCheckbox": { + "message": "Checkbox" + }, "linkedValue": { "message": "Balioa lotuta", "description": "This describes a value that is 'linked' (related) to another value." @@ -1915,6 +1976,43 @@ } } }, + "cardDetails": { + "message": "Card details" + }, + "cardBrandDetails": { + "message": "$BRAND$ details", + "placeholders": { + "brand": { + "content": "$1", + "example": "Visa" + } + } + }, + "learnMoreAboutAuthenticators": { + "message": "Learn more about authenticators" + }, + "copyTOTP": { + "message": "Copy Authenticator key (TOTP)" + }, + "totpHelperTitle": { + "message": "Make 2-step verification seamless" + }, + "totpHelper": { + "message": "Bitwarden can store and fill 2-step verification codes. Copy and paste the key into this field." + }, + "totpHelperWithCapture": { + "message": "Bitwarden can store and fill 2-step verification codes. Select the camera icon to take a screenshot of this website's authenticator QR code, or copy and paste the key into this field." + }, + "premium": { + "message": "Premium", + "description": "Premium membership" + }, + "cardExpiredTitle": { + "message": "Expired card" + }, + "cardExpiredMessage": { + "message": "If you've renewed it, update the card's information" + }, "verificationRequired": { "message": "Verification required", "description": "Default title for the user verification dialog." @@ -2084,6 +2182,15 @@ "personalOwnershipPolicyInEffectImports": { "message": "An organization policy has blocked importing items into your individual vault." }, + "personalDetails": { + "message": "Personal details" + }, + "identification": { + "message": "Identification" + }, + "contactInfo": { + "message": "Contact information" + }, "allSends": { "message": "Send guztiak", "description": "'Sends' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." @@ -2518,6 +2625,9 @@ "generateEmail": { "message": "Generate email" }, + "usernameGenerator": { + "message": "Username generator" + }, "spinboxBoundariesHint": { "message": "Value must be between $MIN$ and $MAX$.", "description": "Explains spin box minimum and maximum values to the user", @@ -3212,6 +3322,9 @@ "followTheStepsBelowToFinishLoggingIn": { "message": "Follow the steps below to finish logging in." }, + "followTheStepsBelowToFinishLoggingInWithSecurityKey": { + "message": "Follow the steps below to finish logging in with your security key." + }, "launchDuo": { "message": "Launch Duo in Browser" }, @@ -3436,6 +3549,17 @@ "ssoError": { "message": "No free ports could be found for the sso login." }, + "securePasswordGenerated": { + "message": "Secure password generated! Don't forget to also update your password on the website." + }, + "useGeneratorHelpTextPartOne": { + "message": "Use the generator", + "description": "This will be used as part of a larger sentence, broken up to include the generator icon. The full sentence will read 'Use the generator [GENERATOR_ICON] to create a strong unique password'" + }, + "useGeneratorHelpTextPartTwo": { + "message": "to create a strong unique password", + "description": "This will be used as part of a larger sentence, broken up to include the generator icon. The full sentence will read 'Use the generator [GENERATOR_ICON] to create a strong unique password'" + }, "biometricsStatusHelptextUnlockNeeded": { "message": "Biometric unlock is unavailable because PIN or password unlock is required first." }, @@ -3514,6 +3638,27 @@ "setupTwoStepLogin": { "message": "Set up two-step login" }, + "itemDetails": { + "message": "Item details" + }, + "itemName": { + "message": "Item name" + }, + "loginCredentials": { + "message": "Login credentials" + }, + "additionalOptions": { + "message": "Additional options" + }, + "itemHistory": { + "message": "Item history" + }, + "lastEdited": { + "message": "Last edited" + }, + "upload": { + "message": "Upload" + }, "newDeviceVerificationNoticeContentPage1": { "message": "Bitwarden will send a code to your account email to verify logins from new devices starting in February 2025." }, @@ -3567,5 +3712,35 @@ }, "move": { "message": "Move" + }, + "newLoginNudgeTitle": { + "message": "Save time with autofill" + }, + "newLoginNudgeBody": { + "message": "Include a Website so this login appears as an autofill suggestion." + }, + "newCardNudgeTitle": { + "message": "Seamless online checkout" + }, + "newCardNudgeBody": { + "message": "With cards, easily autofill payment forms securely and accurately." + }, + "newIdentityNudgeTitle": { + "message": "Simplify creating accounts" + }, + "newIdentityNudgeBody": { + "message": "With identities, quickly autofill long registration or contact forms." + }, + "newNoteNudgeTitle": { + "message": "Keep your sensitive data safe" + }, + "newNoteNudgeBody": { + "message": "With notes, securely store sensitive data like banking or insurance details." + }, + "newSshNudgeTitle": { + "message": "Developer-friendly SSH access" + }, + "newSshNudgeBody": { + "message": "Store your keys and connect with the SSH agent for fast, encrypted authentication." } } diff --git a/apps/desktop/src/locales/fa/messages.json b/apps/desktop/src/locales/fa/messages.json index ef67738c03c..2fcb63b847b 100644 --- a/apps/desktop/src/locales/fa/messages.json +++ b/apps/desktop/src/locales/fa/messages.json @@ -393,6 +393,64 @@ "authenticatorKeyTotp": { "message": "کلید احراز هویت (TOTP)" }, + "authenticatorKey": { + "message": "Authenticator key" + }, + "autofillOptions": { + "message": "Autofill options" + }, + "websiteUri": { + "message": "Website (URI)" + }, + "websiteUriCount": { + "message": "Website (URI) $COUNT$", + "description": "Label for an input field that contains a website URI. The input field is part of a list of fields, and the count indicates the position of the field in the list.", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + } + } + }, + "websiteAdded": { + "message": "Website added" + }, + "addWebsite": { + "message": "Add website" + }, + "deleteWebsite": { + "message": "Delete website" + }, + "owner": { + "message": "Owner" + }, + "addField": { + "message": "Add field" + }, + "fieldType": { + "message": "Field type" + }, + "fieldLabel": { + "message": "Field label" + }, + "add": { + "message": "Add" + }, + "textHelpText": { + "message": "Use text fields for data like security questions" + }, + "hiddenHelpText": { + "message": "Use hidden fields for sensitive data like a password" + }, + "checkBoxHelpText": { + "message": "Use checkboxes if you'd like to autofill a form's checkbox, like a remember email" + }, + "linkedHelpText": { + "message": "Use a linked field when you are experiencing autofill issues for a specific website." + }, + "linkedLabelHelpText": { + "message": "Enter the the field's html id, name, aria-label, or placeholder." + }, "folder": { "message": "پوشه" }, @@ -418,6 +476,9 @@ "message": "پیوند شده", "description": "This describes a field that is 'linked' (related) to another field." }, + "cfTypeCheckbox": { + "message": "Checkbox" + }, "linkedValue": { "message": "مقدار پیوند شده", "description": "This describes a value that is 'linked' (related) to another value." @@ -1915,6 +1976,43 @@ } } }, + "cardDetails": { + "message": "Card details" + }, + "cardBrandDetails": { + "message": "$BRAND$ details", + "placeholders": { + "brand": { + "content": "$1", + "example": "Visa" + } + } + }, + "learnMoreAboutAuthenticators": { + "message": "Learn more about authenticators" + }, + "copyTOTP": { + "message": "Copy Authenticator key (TOTP)" + }, + "totpHelperTitle": { + "message": "Make 2-step verification seamless" + }, + "totpHelper": { + "message": "Bitwarden can store and fill 2-step verification codes. Copy and paste the key into this field." + }, + "totpHelperWithCapture": { + "message": "Bitwarden can store and fill 2-step verification codes. Select the camera icon to take a screenshot of this website's authenticator QR code, or copy and paste the key into this field." + }, + "premium": { + "message": "Premium", + "description": "Premium membership" + }, + "cardExpiredTitle": { + "message": "Expired card" + }, + "cardExpiredMessage": { + "message": "If you've renewed it, update the card's information" + }, "verificationRequired": { "message": "تایید مورد نیاز است", "description": "Default title for the user verification dialog." @@ -2084,6 +2182,15 @@ "personalOwnershipPolicyInEffectImports": { "message": "An organization policy has blocked importing items into your individual vault." }, + "personalDetails": { + "message": "Personal details" + }, + "identification": { + "message": "Identification" + }, + "contactInfo": { + "message": "Contact information" + }, "allSends": { "message": "همه ارسال ها", "description": "'Sends' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." @@ -2518,6 +2625,9 @@ "generateEmail": { "message": "Generate email" }, + "usernameGenerator": { + "message": "Username generator" + }, "spinboxBoundariesHint": { "message": "Value must be between $MIN$ and $MAX$.", "description": "Explains spin box minimum and maximum values to the user", @@ -3212,6 +3322,9 @@ "followTheStepsBelowToFinishLoggingIn": { "message": "Follow the steps below to finish logging in." }, + "followTheStepsBelowToFinishLoggingInWithSecurityKey": { + "message": "Follow the steps below to finish logging in with your security key." + }, "launchDuo": { "message": "Launch Duo in Browser" }, @@ -3436,6 +3549,17 @@ "ssoError": { "message": "No free ports could be found for the sso login." }, + "securePasswordGenerated": { + "message": "Secure password generated! Don't forget to also update your password on the website." + }, + "useGeneratorHelpTextPartOne": { + "message": "Use the generator", + "description": "This will be used as part of a larger sentence, broken up to include the generator icon. The full sentence will read 'Use the generator [GENERATOR_ICON] to create a strong unique password'" + }, + "useGeneratorHelpTextPartTwo": { + "message": "to create a strong unique password", + "description": "This will be used as part of a larger sentence, broken up to include the generator icon. The full sentence will read 'Use the generator [GENERATOR_ICON] to create a strong unique password'" + }, "biometricsStatusHelptextUnlockNeeded": { "message": "Biometric unlock is unavailable because PIN or password unlock is required first." }, @@ -3514,6 +3638,27 @@ "setupTwoStepLogin": { "message": "Set up two-step login" }, + "itemDetails": { + "message": "Item details" + }, + "itemName": { + "message": "Item name" + }, + "loginCredentials": { + "message": "Login credentials" + }, + "additionalOptions": { + "message": "Additional options" + }, + "itemHistory": { + "message": "Item history" + }, + "lastEdited": { + "message": "Last edited" + }, + "upload": { + "message": "Upload" + }, "newDeviceVerificationNoticeContentPage1": { "message": "Bitwarden will send a code to your account email to verify logins from new devices starting in February 2025." }, @@ -3567,5 +3712,35 @@ }, "move": { "message": "Move" + }, + "newLoginNudgeTitle": { + "message": "Save time with autofill" + }, + "newLoginNudgeBody": { + "message": "Include a Website so this login appears as an autofill suggestion." + }, + "newCardNudgeTitle": { + "message": "Seamless online checkout" + }, + "newCardNudgeBody": { + "message": "With cards, easily autofill payment forms securely and accurately." + }, + "newIdentityNudgeTitle": { + "message": "Simplify creating accounts" + }, + "newIdentityNudgeBody": { + "message": "With identities, quickly autofill long registration or contact forms." + }, + "newNoteNudgeTitle": { + "message": "Keep your sensitive data safe" + }, + "newNoteNudgeBody": { + "message": "With notes, securely store sensitive data like banking or insurance details." + }, + "newSshNudgeTitle": { + "message": "Developer-friendly SSH access" + }, + "newSshNudgeBody": { + "message": "Store your keys and connect with the SSH agent for fast, encrypted authentication." } } diff --git a/apps/desktop/src/locales/fi/messages.json b/apps/desktop/src/locales/fi/messages.json index 767a1be15ef..627b65bb4ed 100644 --- a/apps/desktop/src/locales/fi/messages.json +++ b/apps/desktop/src/locales/fi/messages.json @@ -393,6 +393,64 @@ "authenticatorKeyTotp": { "message": "Todennusavain (TOTP)" }, + "authenticatorKey": { + "message": "Todennusavain" + }, + "autofillOptions": { + "message": "Automaattitäytön asetukset" + }, + "websiteUri": { + "message": "Verkkosivusto (URI)" + }, + "websiteUriCount": { + "message": "Verkkosivusto (URI) $COUNT$", + "description": "Label for an input field that contains a website URI. The input field is part of a list of fields, and the count indicates the position of the field in the list.", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + } + } + }, + "websiteAdded": { + "message": "Verkkosivusto lisättiin" + }, + "addWebsite": { + "message": "Lisää verkkosivusto" + }, + "deleteWebsite": { + "message": "Poista verkkosivusto" + }, + "owner": { + "message": "Omistaja" + }, + "addField": { + "message": "Lisää kenttä" + }, + "fieldType": { + "message": "Kentän tyyppi" + }, + "fieldLabel": { + "message": "Kentän nimi" + }, + "add": { + "message": "Lisää" + }, + "textHelpText": { + "message": "Käytä tekstikenttiä esimerkiksi turvakysymysten kaltaisille tiedoille" + }, + "hiddenHelpText": { + "message": "Käytä piilotettuja kenttiä esimerkiksi salasanojen kaltaisille arkaluonteisille tiedoille" + }, + "checkBoxHelpText": { + "message": "Käytä valintaruutuja esimerkiksi sähköpostiosoitteen muistamisen kaltaisten valintaruutujen automaattiseen merkintään" + }, + "linkedHelpText": { + "message": "Käytä linkitettyjä kenttiä kohdatessasi sivustokohtaisia automaattitäytön ongelmia." + }, + "linkedLabelHelpText": { + "message": "Syötä kentän HTML-koodista löytyvä id-, name-, aria-label- tai placeholder-arvo." + }, "folder": { "message": "Kansio" }, @@ -418,6 +476,9 @@ "message": "Linkitetty", "description": "This describes a field that is 'linked' (related) to another field." }, + "cfTypeCheckbox": { + "message": "Valintaruutu" + }, "linkedValue": { "message": "Linkitetty arvo", "description": "This describes a value that is 'linked' (related) to another value." @@ -1915,6 +1976,43 @@ } } }, + "cardDetails": { + "message": "Kortin tiedot" + }, + "cardBrandDetails": { + "message": "$BRAND$-tiedot", + "placeholders": { + "brand": { + "content": "$1", + "example": "Visa" + } + } + }, + "learnMoreAboutAuthenticators": { + "message": "Lue lisää todentajista" + }, + "copyTOTP": { + "message": "Kopioi todennusavain (TOTP)" + }, + "totpHelperTitle": { + "message": "Tee kaksivaiheisesta kirjautumisesta saumatonta" + }, + "totpHelper": { + "message": "Bitwarden voi säilyttää ja täyttää kaksivaiheisen kirjautumisen koodit. Kopioi ja liitä todennusavain tähän kenttään." + }, + "totpHelperWithCapture": { + "message": "Bitwarden voi säilyttää ja täyttää kaksivaiheisen kirjautumisen koodit. Kamerakuvakkeella voit kaapata todennusavaimen avoimen sivun QR-koodista automaattisesti, tai voit kopioida ja liittää sen tähän kenttään manuaalisesti." + }, + "premium": { + "message": "Premium", + "description": "Premium membership" + }, + "cardExpiredTitle": { + "message": "Vanhentunut kortti" + }, + "cardExpiredMessage": { + "message": "Jos olet uudistanut kortin, päivitä sen tiedot" + }, "verificationRequired": { "message": "Vahvistus vaaditaan", "description": "Default title for the user verification dialog." @@ -2073,7 +2171,7 @@ "message": "Yrityskäytännön johdosta kohteiden tallennus henkilökohtaiseen holviin ei ole mahdollista. Muuta omistusasetus organisaatiolle ja valitse käytettävissä olevista kokoelmista." }, "yourNewPasswordCannotBeTheSameAsYourCurrentPassword": { - "message": "Your new password cannot be the same as your current password." + "message": "Uusi salasanasi ei voi olla sama kuin nykyinen salasanasi." }, "hintEqualsPassword": { "message": "Salasanavihjeesi ei voi olla sama kuin salasanasi." @@ -2084,6 +2182,15 @@ "personalOwnershipPolicyInEffectImports": { "message": "Organisaatiokäytäntö estää kohteiden tuonnin yksityiseen holviisi." }, + "personalDetails": { + "message": "Henkilökohtaiset tiedot" + }, + "identification": { + "message": "Identification" + }, + "contactInfo": { + "message": "Yhteystiedot" + }, "allSends": { "message": "Kaikki Sendit", "description": "'Sends' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." @@ -2470,7 +2577,7 @@ } }, "exportingIndividualVaultWithAttachmentsDescription": { - "message": "Only the individual vault items including attachments associated with $EMAIL$ will be exported. Organization vault items will not be included", + "message": "Vain henkilökohtaiset tunnukseen $EMAIL$ liittyvät holvin kohteet liitetiedostoineen viedään. Organisaation holvikohteita ei sisällytetä", "placeholders": { "email": { "content": "$1", @@ -2518,6 +2625,9 @@ "generateEmail": { "message": "Luo sähköpostiosoite" }, + "usernameGenerator": { + "message": "Käyttäjätunnusgeneraattori" + }, "spinboxBoundariesHint": { "message": "Arvon tulee olla väliltä $MIN$—$MAX$.", "description": "Explains spin box minimum and maximum values to the user", @@ -2659,7 +2769,7 @@ } }, "forwaderInvalidOperation": { - "message": "$SERVICENAME$ refused your request. Please contact your service provider for assistance.", + "message": "$SERVICENAME$ hylkäsi pyyntösi. Ole yhteydessä palveluntarjoajaasi saadaksesi apua.", "description": "Displayed when the user is forbidden from using the API by the forwarding service.", "placeholders": { "servicename": { @@ -2669,7 +2779,7 @@ } }, "forwaderInvalidOperationWithMessage": { - "message": "$SERVICENAME$ refused your request: $ERRORMESSAGE$", + "message": "$SERVICENAME$ hylkäsi pyyntösi: $ERRORMESSAGE$", "description": "Displayed when the user is forbidden from using the API by the forwarding service with an error message.", "placeholders": { "servicename": { @@ -2791,7 +2901,7 @@ "message": "Laitteeseesi lähetettiin ilmoitus" }, "notificationSentDevicePart1": { - "message": "Unlock Bitwarden on your device or on the " + "message": "Avaa Bitwarden laitteellasi tai " }, "notificationSentDeviceAnchor": { "message": "web app" @@ -2849,7 +2959,7 @@ "message": "Aika" }, "confirmAccess": { - "message": "Confirm access" + "message": "Myönnä käyttöoikeus" }, "denyAccess": { "message": "Deny access" @@ -3210,7 +3320,10 @@ "message": "Duo edellyttää tililtäsi kaksivaiheista tunnistautumista. Viimeistele kirjautuminen seuraamalla seuraavia vaiheita." }, "followTheStepsBelowToFinishLoggingIn": { - "message": "Follow the steps below to finish logging in." + "message": "Viimeistele kirjautuminen seuraamalla seuraavia vaiheita." + }, + "followTheStepsBelowToFinishLoggingInWithSecurityKey": { + "message": "Follow the steps below to finish logging in with your security key." }, "launchDuo": { "message": "Avaa Duo verkkoselaimessa" @@ -3436,6 +3549,17 @@ "ssoError": { "message": "Kertakirjautumiselle ei löytynyt vapaita portteja." }, + "securePasswordGenerated": { + "message": "Turvallinen salasana luotiin! Muista vaihtaa se myös verkkosivuston tiliasetuksiin." + }, + "useGeneratorHelpTextPartOne": { + "message": "Käytä generaattoria", + "description": "This will be used as part of a larger sentence, broken up to include the generator icon. The full sentence will read 'Use the generator [GENERATOR_ICON] to create a strong unique password'" + }, + "useGeneratorHelpTextPartTwo": { + "message": "to create a strong unique password", + "description": "This will be used as part of a larger sentence, broken up to include the generator icon. The full sentence will read 'Use the generator [GENERATOR_ICON] to create a strong unique password'" + }, "biometricsStatusHelptextUnlockNeeded": { "message": "Biometrinen avaus ei ole käytettävissä, koska PIN-koodi tai salasanan lukituksen avaus vaaditaan ensin." }, @@ -3443,10 +3567,10 @@ "message": "Biometrinen lukituksen avaus ei ole tällä hetkellä käytettävissä." }, "biometricsStatusHelptextAutoSetupNeeded": { - "message": "Biometric unlock is unavailable due to misconfigured system files." + "message": "Biometrinen avaus ei ole käytettävissä virheellisesti määritettyjen järjestelmätiedostojen takia." }, "biometricsStatusHelptextManualSetupNeeded": { - "message": "Biometric unlock is unavailable due to misconfigured system files." + "message": "Biometrinen avaus ei ole käytettävissä virheellisesti määritettyjen järjestelmätiedostojen takia." }, "biometricsStatusHelptextNotEnabledLocally": { "message": "Biometrinen avaus ei ole käytettävissä, koska sitä ei ole otettu käyttöön osoitteelle $EMAIL$ Bitwardenin työpöytäsovelluksessa.", @@ -3473,7 +3597,7 @@ "message": "Warning: Agent Forwarding" }, "agentForwardingWarningText": { - "message": "This request comes from a remote device that you are logged into" + "message": "Tämä pyyntö tulee etälaitteelta, johon olet kirjautunut" }, "sshkeyApprovalMessageInfix": { "message": "pyytää pääsyä" @@ -3503,7 +3627,7 @@ "message": "Tuo avain leikepöydältä" }, "sshKeyImported": { - "message": "SSH key imported successfully" + "message": "SSH-avain on tuotu" }, "fileSavedToDevice": { "message": "Tiedosto tallennettiin laitteelle. Hallitse sitä laitteesi latauksista." @@ -3514,6 +3638,27 @@ "setupTwoStepLogin": { "message": "Määritä kaksivaiheinen kirjautuminen" }, + "itemDetails": { + "message": "Kohteen tiedot" + }, + "itemName": { + "message": "Kohteen nimi" + }, + "loginCredentials": { + "message": "Kirjautumistiedot" + }, + "additionalOptions": { + "message": "Lisävalinnat" + }, + "itemHistory": { + "message": "Kohteen historia" + }, + "lastEdited": { + "message": "Last edited" + }, + "upload": { + "message": "Lähetä" + }, "newDeviceVerificationNoticeContentPage1": { "message": "Bitwarden lähettää tilisi sähköpostiosoitteeseen koodin, jolla voit vahvistaa kirjautumiset uusista laitteista helmikuusta 2025 alkaen." }, @@ -3524,7 +3669,7 @@ "message": "Muistuta myöhemmin" }, "newDeviceVerificationNoticePageOneFormContent": { - "message": "Do you have reliable access to your email, $EMAIL$?", + "message": "Onko sinulla luotettava pääsy sähköpostiisi, $EMAIL$?", "placeholders": { "email": { "content": "$1", @@ -3533,10 +3678,10 @@ } }, "newDeviceVerificationNoticePageOneEmailAccessNo": { - "message": "No, I do not" + "message": "Ei ole" }, "newDeviceVerificationNoticePageOneEmailAccessYes": { - "message": "Yes, I can reliably access my email" + "message": "Kyllä, voin käyttää sähköpostiani luotettavasti" }, "turnOnTwoStepLogin": { "message": "Ota kaksivaiheinen kirjautuminen käyttöön" @@ -3566,6 +3711,36 @@ "message": "Vaihda vaarantunut salasana" }, "move": { - "message": "Move" + "message": "Siirrä" + }, + "newLoginNudgeTitle": { + "message": "Save time with autofill" + }, + "newLoginNudgeBody": { + "message": "Include a Website so this login appears as an autofill suggestion." + }, + "newCardNudgeTitle": { + "message": "Seamless online checkout" + }, + "newCardNudgeBody": { + "message": "With cards, easily autofill payment forms securely and accurately." + }, + "newIdentityNudgeTitle": { + "message": "Simplify creating accounts" + }, + "newIdentityNudgeBody": { + "message": "With identities, quickly autofill long registration or contact forms." + }, + "newNoteNudgeTitle": { + "message": "Keep your sensitive data safe" + }, + "newNoteNudgeBody": { + "message": "With notes, securely store sensitive data like banking or insurance details." + }, + "newSshNudgeTitle": { + "message": "Developer-friendly SSH access" + }, + "newSshNudgeBody": { + "message": "Store your keys and connect with the SSH agent for fast, encrypted authentication." } } diff --git a/apps/desktop/src/locales/fil/messages.json b/apps/desktop/src/locales/fil/messages.json index 597dd84d781..2735aef242e 100644 --- a/apps/desktop/src/locales/fil/messages.json +++ b/apps/desktop/src/locales/fil/messages.json @@ -393,6 +393,64 @@ "authenticatorKeyTotp": { "message": "Susi ng Authenticator (TOTP)" }, + "authenticatorKey": { + "message": "Authenticator key" + }, + "autofillOptions": { + "message": "Autofill options" + }, + "websiteUri": { + "message": "Website (URI)" + }, + "websiteUriCount": { + "message": "Website (URI) $COUNT$", + "description": "Label for an input field that contains a website URI. The input field is part of a list of fields, and the count indicates the position of the field in the list.", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + } + } + }, + "websiteAdded": { + "message": "Website added" + }, + "addWebsite": { + "message": "Add website" + }, + "deleteWebsite": { + "message": "Delete website" + }, + "owner": { + "message": "Owner" + }, + "addField": { + "message": "Add field" + }, + "fieldType": { + "message": "Field type" + }, + "fieldLabel": { + "message": "Field label" + }, + "add": { + "message": "Add" + }, + "textHelpText": { + "message": "Use text fields for data like security questions" + }, + "hiddenHelpText": { + "message": "Use hidden fields for sensitive data like a password" + }, + "checkBoxHelpText": { + "message": "Use checkboxes if you'd like to autofill a form's checkbox, like a remember email" + }, + "linkedHelpText": { + "message": "Use a linked field when you are experiencing autofill issues for a specific website." + }, + "linkedLabelHelpText": { + "message": "Enter the the field's html id, name, aria-label, or placeholder." + }, "folder": { "message": "Folder/Direktoryo" }, @@ -418,6 +476,9 @@ "message": "Nilikha", "description": "This describes a field that is 'linked' (related) to another field." }, + "cfTypeCheckbox": { + "message": "Checkbox" + }, "linkedValue": { "message": "Naka link na halaga", "description": "This describes a value that is 'linked' (related) to another value." @@ -1915,6 +1976,43 @@ } } }, + "cardDetails": { + "message": "Card details" + }, + "cardBrandDetails": { + "message": "$BRAND$ details", + "placeholders": { + "brand": { + "content": "$1", + "example": "Visa" + } + } + }, + "learnMoreAboutAuthenticators": { + "message": "Learn more about authenticators" + }, + "copyTOTP": { + "message": "Copy Authenticator key (TOTP)" + }, + "totpHelperTitle": { + "message": "Make 2-step verification seamless" + }, + "totpHelper": { + "message": "Bitwarden can store and fill 2-step verification codes. Copy and paste the key into this field." + }, + "totpHelperWithCapture": { + "message": "Bitwarden can store and fill 2-step verification codes. Select the camera icon to take a screenshot of this website's authenticator QR code, or copy and paste the key into this field." + }, + "premium": { + "message": "Premium", + "description": "Premium membership" + }, + "cardExpiredTitle": { + "message": "Expired card" + }, + "cardExpiredMessage": { + "message": "If you've renewed it, update the card's information" + }, "verificationRequired": { "message": "Verification required", "description": "Default title for the user verification dialog." @@ -2084,6 +2182,15 @@ "personalOwnershipPolicyInEffectImports": { "message": "An organization policy has blocked importing items into your individual vault." }, + "personalDetails": { + "message": "Personal details" + }, + "identification": { + "message": "Identification" + }, + "contactInfo": { + "message": "Contact information" + }, "allSends": { "message": "Lahat ng Mga Padala", "description": "'Sends' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." @@ -2518,6 +2625,9 @@ "generateEmail": { "message": "Generate email" }, + "usernameGenerator": { + "message": "Username generator" + }, "spinboxBoundariesHint": { "message": "Value must be between $MIN$ and $MAX$.", "description": "Explains spin box minimum and maximum values to the user", @@ -3212,6 +3322,9 @@ "followTheStepsBelowToFinishLoggingIn": { "message": "Follow the steps below to finish logging in." }, + "followTheStepsBelowToFinishLoggingInWithSecurityKey": { + "message": "Follow the steps below to finish logging in with your security key." + }, "launchDuo": { "message": "Launch Duo in Browser" }, @@ -3436,6 +3549,17 @@ "ssoError": { "message": "No free ports could be found for the sso login." }, + "securePasswordGenerated": { + "message": "Secure password generated! Don't forget to also update your password on the website." + }, + "useGeneratorHelpTextPartOne": { + "message": "Use the generator", + "description": "This will be used as part of a larger sentence, broken up to include the generator icon. The full sentence will read 'Use the generator [GENERATOR_ICON] to create a strong unique password'" + }, + "useGeneratorHelpTextPartTwo": { + "message": "to create a strong unique password", + "description": "This will be used as part of a larger sentence, broken up to include the generator icon. The full sentence will read 'Use the generator [GENERATOR_ICON] to create a strong unique password'" + }, "biometricsStatusHelptextUnlockNeeded": { "message": "Biometric unlock is unavailable because PIN or password unlock is required first." }, @@ -3514,6 +3638,27 @@ "setupTwoStepLogin": { "message": "Set up two-step login" }, + "itemDetails": { + "message": "Item details" + }, + "itemName": { + "message": "Item name" + }, + "loginCredentials": { + "message": "Login credentials" + }, + "additionalOptions": { + "message": "Additional options" + }, + "itemHistory": { + "message": "Item history" + }, + "lastEdited": { + "message": "Last edited" + }, + "upload": { + "message": "Upload" + }, "newDeviceVerificationNoticeContentPage1": { "message": "Bitwarden will send a code to your account email to verify logins from new devices starting in February 2025." }, @@ -3567,5 +3712,35 @@ }, "move": { "message": "Move" + }, + "newLoginNudgeTitle": { + "message": "Save time with autofill" + }, + "newLoginNudgeBody": { + "message": "Include a Website so this login appears as an autofill suggestion." + }, + "newCardNudgeTitle": { + "message": "Seamless online checkout" + }, + "newCardNudgeBody": { + "message": "With cards, easily autofill payment forms securely and accurately." + }, + "newIdentityNudgeTitle": { + "message": "Simplify creating accounts" + }, + "newIdentityNudgeBody": { + "message": "With identities, quickly autofill long registration or contact forms." + }, + "newNoteNudgeTitle": { + "message": "Keep your sensitive data safe" + }, + "newNoteNudgeBody": { + "message": "With notes, securely store sensitive data like banking or insurance details." + }, + "newSshNudgeTitle": { + "message": "Developer-friendly SSH access" + }, + "newSshNudgeBody": { + "message": "Store your keys and connect with the SSH agent for fast, encrypted authentication." } } diff --git a/apps/desktop/src/locales/fr/messages.json b/apps/desktop/src/locales/fr/messages.json index 94edd101367..0170cf2d0ce 100644 --- a/apps/desktop/src/locales/fr/messages.json +++ b/apps/desktop/src/locales/fr/messages.json @@ -393,6 +393,64 @@ "authenticatorKeyTotp": { "message": "Clé d'authentification (TOTP)" }, + "authenticatorKey": { + "message": "Clé d'authentification" + }, + "autofillOptions": { + "message": "Options de saisie automatique" + }, + "websiteUri": { + "message": "Site web (URI)" + }, + "websiteUriCount": { + "message": "Site web (URI) $COUNT$", + "description": "Label for an input field that contains a website URI. The input field is part of a list of fields, and the count indicates the position of the field in the list.", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + } + } + }, + "websiteAdded": { + "message": "Site web ajouté" + }, + "addWebsite": { + "message": "Ajouter le site web" + }, + "deleteWebsite": { + "message": "Supprimer le site web" + }, + "owner": { + "message": "Propriétaire" + }, + "addField": { + "message": "Ajouter un champ" + }, + "fieldType": { + "message": "Type de champ" + }, + "fieldLabel": { + "message": "Étiquette du champ" + }, + "add": { + "message": "Ajouter" + }, + "textHelpText": { + "message": "Utiliser des champs de texte pour les données comme les questions de sécurité" + }, + "hiddenHelpText": { + "message": "Utiliser des champs cachés pour des données sensibles comme un mot de passe" + }, + "checkBoxHelpText": { + "message": "Use checkboxes if you'd like to autofill a form's checkbox, like a remember email" + }, + "linkedHelpText": { + "message": "Use a linked field when you are experiencing autofill issues for a specific website." + }, + "linkedLabelHelpText": { + "message": "Enter the the field's html id, name, aria-label, or placeholder." + }, "folder": { "message": "Dossier" }, @@ -418,6 +476,9 @@ "message": "Lié", "description": "This describes a field that is 'linked' (related) to another field." }, + "cfTypeCheckbox": { + "message": "Case à cocher" + }, "linkedValue": { "message": "Valeur liée", "description": "This describes a value that is 'linked' (related) to another value." @@ -1915,6 +1976,43 @@ } } }, + "cardDetails": { + "message": "Card details" + }, + "cardBrandDetails": { + "message": "$BRAND$ details", + "placeholders": { + "brand": { + "content": "$1", + "example": "Visa" + } + } + }, + "learnMoreAboutAuthenticators": { + "message": "Learn more about authenticators" + }, + "copyTOTP": { + "message": "Copy Authenticator key (TOTP)" + }, + "totpHelperTitle": { + "message": "Make 2-step verification seamless" + }, + "totpHelper": { + "message": "Bitwarden can store and fill 2-step verification codes. Copy and paste the key into this field." + }, + "totpHelperWithCapture": { + "message": "Bitwarden can store and fill 2-step verification codes. Select the camera icon to take a screenshot of this website's authenticator QR code, or copy and paste the key into this field." + }, + "premium": { + "message": "Premium", + "description": "Premium membership" + }, + "cardExpiredTitle": { + "message": "Expired card" + }, + "cardExpiredMessage": { + "message": "If you've renewed it, update the card's information" + }, "verificationRequired": { "message": "Vérification requise", "description": "Default title for the user verification dialog." @@ -2084,6 +2182,15 @@ "personalOwnershipPolicyInEffectImports": { "message": "Une politique d'organisation a bloqué l'import d'éléments dans votre coffre personel." }, + "personalDetails": { + "message": "Personal details" + }, + "identification": { + "message": "Identification" + }, + "contactInfo": { + "message": "Contact information" + }, "allSends": { "message": "Tous les Sends", "description": "'Sends' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." @@ -2518,6 +2625,9 @@ "generateEmail": { "message": "Générer un courriel" }, + "usernameGenerator": { + "message": "Username generator" + }, "spinboxBoundariesHint": { "message": "La valeur doit être comprise entre $MIN$ et $MAX$.", "description": "Explains spin box minimum and maximum values to the user", @@ -3212,6 +3322,9 @@ "followTheStepsBelowToFinishLoggingIn": { "message": "Suivez les étapes ci-dessous afin de réussir à vous connecter." }, + "followTheStepsBelowToFinishLoggingInWithSecurityKey": { + "message": "Follow the steps below to finish logging in with your security key." + }, "launchDuo": { "message": "Lancer Duo dans le navigateur" }, @@ -3436,6 +3549,17 @@ "ssoError": { "message": "Aucun port libre n’a pu être trouvé pour la connexion SSO." }, + "securePasswordGenerated": { + "message": "Secure password generated! Don't forget to also update your password on the website." + }, + "useGeneratorHelpTextPartOne": { + "message": "Use the generator", + "description": "This will be used as part of a larger sentence, broken up to include the generator icon. The full sentence will read 'Use the generator [GENERATOR_ICON] to create a strong unique password'" + }, + "useGeneratorHelpTextPartTwo": { + "message": "to create a strong unique password", + "description": "This will be used as part of a larger sentence, broken up to include the generator icon. The full sentence will read 'Use the generator [GENERATOR_ICON] to create a strong unique password'" + }, "biometricsStatusHelptextUnlockNeeded": { "message": "Le déverrouillage par biométrie n’est pas disponible parce qu'il faut au préalable déverrouiller avec le code PIN ou le mot de passe." }, @@ -3514,6 +3638,27 @@ "setupTwoStepLogin": { "message": "Configurer l'identification à deux facteurs" }, + "itemDetails": { + "message": "Item details" + }, + "itemName": { + "message": "Item name" + }, + "loginCredentials": { + "message": "Login credentials" + }, + "additionalOptions": { + "message": "Additional options" + }, + "itemHistory": { + "message": "Item history" + }, + "lastEdited": { + "message": "Last edited" + }, + "upload": { + "message": "Upload" + }, "newDeviceVerificationNoticeContentPage1": { "message": "Bitwarden enverra un code au courriel de votre compte pour vérifier les connexions depuis de nouveaux appareils à partir de février 2025." }, @@ -3567,5 +3712,35 @@ }, "move": { "message": "Move" + }, + "newLoginNudgeTitle": { + "message": "Save time with autofill" + }, + "newLoginNudgeBody": { + "message": "Include a Website so this login appears as an autofill suggestion." + }, + "newCardNudgeTitle": { + "message": "Seamless online checkout" + }, + "newCardNudgeBody": { + "message": "With cards, easily autofill payment forms securely and accurately." + }, + "newIdentityNudgeTitle": { + "message": "Simplify creating accounts" + }, + "newIdentityNudgeBody": { + "message": "With identities, quickly autofill long registration or contact forms." + }, + "newNoteNudgeTitle": { + "message": "Keep your sensitive data safe" + }, + "newNoteNudgeBody": { + "message": "With notes, securely store sensitive data like banking or insurance details." + }, + "newSshNudgeTitle": { + "message": "Developer-friendly SSH access" + }, + "newSshNudgeBody": { + "message": "Store your keys and connect with the SSH agent for fast, encrypted authentication." } } diff --git a/apps/desktop/src/locales/gl/messages.json b/apps/desktop/src/locales/gl/messages.json index 8850cbe5a3f..2350e0df4c7 100644 --- a/apps/desktop/src/locales/gl/messages.json +++ b/apps/desktop/src/locales/gl/messages.json @@ -393,6 +393,64 @@ "authenticatorKeyTotp": { "message": "Authenticator key (TOTP)" }, + "authenticatorKey": { + "message": "Authenticator key" + }, + "autofillOptions": { + "message": "Autofill options" + }, + "websiteUri": { + "message": "Website (URI)" + }, + "websiteUriCount": { + "message": "Website (URI) $COUNT$", + "description": "Label for an input field that contains a website URI. The input field is part of a list of fields, and the count indicates the position of the field in the list.", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + } + } + }, + "websiteAdded": { + "message": "Website added" + }, + "addWebsite": { + "message": "Add website" + }, + "deleteWebsite": { + "message": "Delete website" + }, + "owner": { + "message": "Owner" + }, + "addField": { + "message": "Add field" + }, + "fieldType": { + "message": "Field type" + }, + "fieldLabel": { + "message": "Field label" + }, + "add": { + "message": "Add" + }, + "textHelpText": { + "message": "Use text fields for data like security questions" + }, + "hiddenHelpText": { + "message": "Use hidden fields for sensitive data like a password" + }, + "checkBoxHelpText": { + "message": "Use checkboxes if you'd like to autofill a form's checkbox, like a remember email" + }, + "linkedHelpText": { + "message": "Use a linked field when you are experiencing autofill issues for a specific website." + }, + "linkedLabelHelpText": { + "message": "Enter the the field's html id, name, aria-label, or placeholder." + }, "folder": { "message": "Folder" }, @@ -418,6 +476,9 @@ "message": "Linked", "description": "This describes a field that is 'linked' (related) to another field." }, + "cfTypeCheckbox": { + "message": "Checkbox" + }, "linkedValue": { "message": "Linked value", "description": "This describes a value that is 'linked' (related) to another value." @@ -1915,6 +1976,43 @@ } } }, + "cardDetails": { + "message": "Card details" + }, + "cardBrandDetails": { + "message": "$BRAND$ details", + "placeholders": { + "brand": { + "content": "$1", + "example": "Visa" + } + } + }, + "learnMoreAboutAuthenticators": { + "message": "Learn more about authenticators" + }, + "copyTOTP": { + "message": "Copy Authenticator key (TOTP)" + }, + "totpHelperTitle": { + "message": "Make 2-step verification seamless" + }, + "totpHelper": { + "message": "Bitwarden can store and fill 2-step verification codes. Copy and paste the key into this field." + }, + "totpHelperWithCapture": { + "message": "Bitwarden can store and fill 2-step verification codes. Select the camera icon to take a screenshot of this website's authenticator QR code, or copy and paste the key into this field." + }, + "premium": { + "message": "Premium", + "description": "Premium membership" + }, + "cardExpiredTitle": { + "message": "Expired card" + }, + "cardExpiredMessage": { + "message": "If you've renewed it, update the card's information" + }, "verificationRequired": { "message": "Verification required", "description": "Default title for the user verification dialog." @@ -2084,6 +2182,15 @@ "personalOwnershipPolicyInEffectImports": { "message": "An organization policy has blocked importing items into your individual vault." }, + "personalDetails": { + "message": "Personal details" + }, + "identification": { + "message": "Identification" + }, + "contactInfo": { + "message": "Contact information" + }, "allSends": { "message": "All Sends", "description": "'Sends' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." @@ -2518,6 +2625,9 @@ "generateEmail": { "message": "Generate email" }, + "usernameGenerator": { + "message": "Username generator" + }, "spinboxBoundariesHint": { "message": "Value must be between $MIN$ and $MAX$.", "description": "Explains spin box minimum and maximum values to the user", @@ -3212,6 +3322,9 @@ "followTheStepsBelowToFinishLoggingIn": { "message": "Follow the steps below to finish logging in." }, + "followTheStepsBelowToFinishLoggingInWithSecurityKey": { + "message": "Follow the steps below to finish logging in with your security key." + }, "launchDuo": { "message": "Launch Duo in Browser" }, @@ -3436,6 +3549,17 @@ "ssoError": { "message": "No free ports could be found for the sso login." }, + "securePasswordGenerated": { + "message": "Secure password generated! Don't forget to also update your password on the website." + }, + "useGeneratorHelpTextPartOne": { + "message": "Use the generator", + "description": "This will be used as part of a larger sentence, broken up to include the generator icon. The full sentence will read 'Use the generator [GENERATOR_ICON] to create a strong unique password'" + }, + "useGeneratorHelpTextPartTwo": { + "message": "to create a strong unique password", + "description": "This will be used as part of a larger sentence, broken up to include the generator icon. The full sentence will read 'Use the generator [GENERATOR_ICON] to create a strong unique password'" + }, "biometricsStatusHelptextUnlockNeeded": { "message": "Biometric unlock is unavailable because PIN or password unlock is required first." }, @@ -3514,6 +3638,27 @@ "setupTwoStepLogin": { "message": "Set up two-step login" }, + "itemDetails": { + "message": "Item details" + }, + "itemName": { + "message": "Item name" + }, + "loginCredentials": { + "message": "Login credentials" + }, + "additionalOptions": { + "message": "Additional options" + }, + "itemHistory": { + "message": "Item history" + }, + "lastEdited": { + "message": "Last edited" + }, + "upload": { + "message": "Upload" + }, "newDeviceVerificationNoticeContentPage1": { "message": "Bitwarden will send a code to your account email to verify logins from new devices starting in February 2025." }, @@ -3567,5 +3712,35 @@ }, "move": { "message": "Move" + }, + "newLoginNudgeTitle": { + "message": "Save time with autofill" + }, + "newLoginNudgeBody": { + "message": "Include a Website so this login appears as an autofill suggestion." + }, + "newCardNudgeTitle": { + "message": "Seamless online checkout" + }, + "newCardNudgeBody": { + "message": "With cards, easily autofill payment forms securely and accurately." + }, + "newIdentityNudgeTitle": { + "message": "Simplify creating accounts" + }, + "newIdentityNudgeBody": { + "message": "With identities, quickly autofill long registration or contact forms." + }, + "newNoteNudgeTitle": { + "message": "Keep your sensitive data safe" + }, + "newNoteNudgeBody": { + "message": "With notes, securely store sensitive data like banking or insurance details." + }, + "newSshNudgeTitle": { + "message": "Developer-friendly SSH access" + }, + "newSshNudgeBody": { + "message": "Store your keys and connect with the SSH agent for fast, encrypted authentication." } } diff --git a/apps/desktop/src/locales/he/messages.json b/apps/desktop/src/locales/he/messages.json index 46288ba0b63..a8f52de239f 100644 --- a/apps/desktop/src/locales/he/messages.json +++ b/apps/desktop/src/locales/he/messages.json @@ -393,6 +393,64 @@ "authenticatorKeyTotp": { "message": "מפתח אימות (TOTP)" }, + "authenticatorKey": { + "message": "Authenticator key" + }, + "autofillOptions": { + "message": "Autofill options" + }, + "websiteUri": { + "message": "Website (URI)" + }, + "websiteUriCount": { + "message": "Website (URI) $COUNT$", + "description": "Label for an input field that contains a website URI. The input field is part of a list of fields, and the count indicates the position of the field in the list.", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + } + } + }, + "websiteAdded": { + "message": "Website added" + }, + "addWebsite": { + "message": "Add website" + }, + "deleteWebsite": { + "message": "Delete website" + }, + "owner": { + "message": "Owner" + }, + "addField": { + "message": "Add field" + }, + "fieldType": { + "message": "Field type" + }, + "fieldLabel": { + "message": "Field label" + }, + "add": { + "message": "Add" + }, + "textHelpText": { + "message": "Use text fields for data like security questions" + }, + "hiddenHelpText": { + "message": "Use hidden fields for sensitive data like a password" + }, + "checkBoxHelpText": { + "message": "Use checkboxes if you'd like to autofill a form's checkbox, like a remember email" + }, + "linkedHelpText": { + "message": "Use a linked field when you are experiencing autofill issues for a specific website." + }, + "linkedLabelHelpText": { + "message": "Enter the the field's html id, name, aria-label, or placeholder." + }, "folder": { "message": "תיקייה" }, @@ -418,6 +476,9 @@ "message": "מקושר", "description": "This describes a field that is 'linked' (related) to another field." }, + "cfTypeCheckbox": { + "message": "Checkbox" + }, "linkedValue": { "message": "ערך מקושר", "description": "This describes a value that is 'linked' (related) to another value." @@ -1915,6 +1976,43 @@ } } }, + "cardDetails": { + "message": "Card details" + }, + "cardBrandDetails": { + "message": "$BRAND$ details", + "placeholders": { + "brand": { + "content": "$1", + "example": "Visa" + } + } + }, + "learnMoreAboutAuthenticators": { + "message": "Learn more about authenticators" + }, + "copyTOTP": { + "message": "Copy Authenticator key (TOTP)" + }, + "totpHelperTitle": { + "message": "Make 2-step verification seamless" + }, + "totpHelper": { + "message": "Bitwarden can store and fill 2-step verification codes. Copy and paste the key into this field." + }, + "totpHelperWithCapture": { + "message": "Bitwarden can store and fill 2-step verification codes. Select the camera icon to take a screenshot of this website's authenticator QR code, or copy and paste the key into this field." + }, + "premium": { + "message": "Premium", + "description": "Premium membership" + }, + "cardExpiredTitle": { + "message": "Expired card" + }, + "cardExpiredMessage": { + "message": "If you've renewed it, update the card's information" + }, "verificationRequired": { "message": "נדרש אימות", "description": "Default title for the user verification dialog." @@ -2084,6 +2182,15 @@ "personalOwnershipPolicyInEffectImports": { "message": "מדיניות ארגון חסמה ייבוא פריטים אל תוך הכספת האישית שלך." }, + "personalDetails": { + "message": "Personal details" + }, + "identification": { + "message": "Identification" + }, + "contactInfo": { + "message": "Contact information" + }, "allSends": { "message": "כל הסֵנְדים", "description": "'Sends' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." @@ -2518,6 +2625,9 @@ "generateEmail": { "message": "צור דוא\"ל" }, + "usernameGenerator": { + "message": "Username generator" + }, "spinboxBoundariesHint": { "message": "הערך חייב להיות בין $MIN$ ל־$MAX$.", "description": "Explains spin box minimum and maximum values to the user", @@ -3212,6 +3322,9 @@ "followTheStepsBelowToFinishLoggingIn": { "message": "עקוב אחר השלבים למטה כדי לסיים להיכנס." }, + "followTheStepsBelowToFinishLoggingInWithSecurityKey": { + "message": "Follow the steps below to finish logging in with your security key." + }, "launchDuo": { "message": "פתח את Duo בדפדפן" }, @@ -3436,6 +3549,17 @@ "ssoError": { "message": "לא נמצאו יציאות פנויות עבור כניסת ה־sso." }, + "securePasswordGenerated": { + "message": "Secure password generated! Don't forget to also update your password on the website." + }, + "useGeneratorHelpTextPartOne": { + "message": "Use the generator", + "description": "This will be used as part of a larger sentence, broken up to include the generator icon. The full sentence will read 'Use the generator [GENERATOR_ICON] to create a strong unique password'" + }, + "useGeneratorHelpTextPartTwo": { + "message": "to create a strong unique password", + "description": "This will be used as part of a larger sentence, broken up to include the generator icon. The full sentence will read 'Use the generator [GENERATOR_ICON] to create a strong unique password'" + }, "biometricsStatusHelptextUnlockNeeded": { "message": "ביטול נעילה ביומטרי אינו זמין בגלל שביטול נעילה עם PIN או סיסמה נדרש תחילה." }, @@ -3514,6 +3638,27 @@ "setupTwoStepLogin": { "message": "הגדר כניסה דו־שלבית" }, + "itemDetails": { + "message": "Item details" + }, + "itemName": { + "message": "Item name" + }, + "loginCredentials": { + "message": "Login credentials" + }, + "additionalOptions": { + "message": "Additional options" + }, + "itemHistory": { + "message": "Item history" + }, + "lastEdited": { + "message": "Last edited" + }, + "upload": { + "message": "Upload" + }, "newDeviceVerificationNoticeContentPage1": { "message": "Bitwarden ישלח קוד לדוא\"ל החשבון שלך כדי לאמת כניסות ממכשירים חדשים החל מפברואר 2025." }, @@ -3567,5 +3712,35 @@ }, "move": { "message": "Move" + }, + "newLoginNudgeTitle": { + "message": "Save time with autofill" + }, + "newLoginNudgeBody": { + "message": "Include a Website so this login appears as an autofill suggestion." + }, + "newCardNudgeTitle": { + "message": "Seamless online checkout" + }, + "newCardNudgeBody": { + "message": "With cards, easily autofill payment forms securely and accurately." + }, + "newIdentityNudgeTitle": { + "message": "Simplify creating accounts" + }, + "newIdentityNudgeBody": { + "message": "With identities, quickly autofill long registration or contact forms." + }, + "newNoteNudgeTitle": { + "message": "Keep your sensitive data safe" + }, + "newNoteNudgeBody": { + "message": "With notes, securely store sensitive data like banking or insurance details." + }, + "newSshNudgeTitle": { + "message": "Developer-friendly SSH access" + }, + "newSshNudgeBody": { + "message": "Store your keys and connect with the SSH agent for fast, encrypted authentication." } } diff --git a/apps/desktop/src/locales/hi/messages.json b/apps/desktop/src/locales/hi/messages.json index 27a9eb7e92f..a5d273d0e25 100644 --- a/apps/desktop/src/locales/hi/messages.json +++ b/apps/desktop/src/locales/hi/messages.json @@ -393,6 +393,64 @@ "authenticatorKeyTotp": { "message": "Authenticator key (TOTP)" }, + "authenticatorKey": { + "message": "Authenticator key" + }, + "autofillOptions": { + "message": "Autofill options" + }, + "websiteUri": { + "message": "Website (URI)" + }, + "websiteUriCount": { + "message": "Website (URI) $COUNT$", + "description": "Label for an input field that contains a website URI. The input field is part of a list of fields, and the count indicates the position of the field in the list.", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + } + } + }, + "websiteAdded": { + "message": "Website added" + }, + "addWebsite": { + "message": "Add website" + }, + "deleteWebsite": { + "message": "Delete website" + }, + "owner": { + "message": "Owner" + }, + "addField": { + "message": "Add field" + }, + "fieldType": { + "message": "Field type" + }, + "fieldLabel": { + "message": "Field label" + }, + "add": { + "message": "Add" + }, + "textHelpText": { + "message": "Use text fields for data like security questions" + }, + "hiddenHelpText": { + "message": "Use hidden fields for sensitive data like a password" + }, + "checkBoxHelpText": { + "message": "Use checkboxes if you'd like to autofill a form's checkbox, like a remember email" + }, + "linkedHelpText": { + "message": "Use a linked field when you are experiencing autofill issues for a specific website." + }, + "linkedLabelHelpText": { + "message": "Enter the the field's html id, name, aria-label, or placeholder." + }, "folder": { "message": "Folder" }, @@ -418,6 +476,9 @@ "message": "Linked", "description": "This describes a field that is 'linked' (related) to another field." }, + "cfTypeCheckbox": { + "message": "Checkbox" + }, "linkedValue": { "message": "Linked value", "description": "This describes a value that is 'linked' (related) to another value." @@ -1915,6 +1976,43 @@ } } }, + "cardDetails": { + "message": "Card details" + }, + "cardBrandDetails": { + "message": "$BRAND$ details", + "placeholders": { + "brand": { + "content": "$1", + "example": "Visa" + } + } + }, + "learnMoreAboutAuthenticators": { + "message": "Learn more about authenticators" + }, + "copyTOTP": { + "message": "Copy Authenticator key (TOTP)" + }, + "totpHelperTitle": { + "message": "Make 2-step verification seamless" + }, + "totpHelper": { + "message": "Bitwarden can store and fill 2-step verification codes. Copy and paste the key into this field." + }, + "totpHelperWithCapture": { + "message": "Bitwarden can store and fill 2-step verification codes. Select the camera icon to take a screenshot of this website's authenticator QR code, or copy and paste the key into this field." + }, + "premium": { + "message": "Premium", + "description": "Premium membership" + }, + "cardExpiredTitle": { + "message": "Expired card" + }, + "cardExpiredMessage": { + "message": "If you've renewed it, update the card's information" + }, "verificationRequired": { "message": "Verification required", "description": "Default title for the user verification dialog." @@ -2084,6 +2182,15 @@ "personalOwnershipPolicyInEffectImports": { "message": "An organization policy has blocked importing items into your individual vault." }, + "personalDetails": { + "message": "Personal details" + }, + "identification": { + "message": "Identification" + }, + "contactInfo": { + "message": "Contact information" + }, "allSends": { "message": "All Sends", "description": "'Sends' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." @@ -2518,6 +2625,9 @@ "generateEmail": { "message": "Generate email" }, + "usernameGenerator": { + "message": "Username generator" + }, "spinboxBoundariesHint": { "message": "Value must be between $MIN$ and $MAX$.", "description": "Explains spin box minimum and maximum values to the user", @@ -3212,6 +3322,9 @@ "followTheStepsBelowToFinishLoggingIn": { "message": "Follow the steps below to finish logging in." }, + "followTheStepsBelowToFinishLoggingInWithSecurityKey": { + "message": "Follow the steps below to finish logging in with your security key." + }, "launchDuo": { "message": "Launch Duo in Browser" }, @@ -3436,6 +3549,17 @@ "ssoError": { "message": "No free ports could be found for the sso login." }, + "securePasswordGenerated": { + "message": "Secure password generated! Don't forget to also update your password on the website." + }, + "useGeneratorHelpTextPartOne": { + "message": "Use the generator", + "description": "This will be used as part of a larger sentence, broken up to include the generator icon. The full sentence will read 'Use the generator [GENERATOR_ICON] to create a strong unique password'" + }, + "useGeneratorHelpTextPartTwo": { + "message": "to create a strong unique password", + "description": "This will be used as part of a larger sentence, broken up to include the generator icon. The full sentence will read 'Use the generator [GENERATOR_ICON] to create a strong unique password'" + }, "biometricsStatusHelptextUnlockNeeded": { "message": "Biometric unlock is unavailable because PIN or password unlock is required first." }, @@ -3514,6 +3638,27 @@ "setupTwoStepLogin": { "message": "Set up two-step login" }, + "itemDetails": { + "message": "Item details" + }, + "itemName": { + "message": "Item name" + }, + "loginCredentials": { + "message": "Login credentials" + }, + "additionalOptions": { + "message": "Additional options" + }, + "itemHistory": { + "message": "Item history" + }, + "lastEdited": { + "message": "Last edited" + }, + "upload": { + "message": "Upload" + }, "newDeviceVerificationNoticeContentPage1": { "message": "Bitwarden will send a code to your account email to verify logins from new devices starting in February 2025." }, @@ -3567,5 +3712,35 @@ }, "move": { "message": "Move" + }, + "newLoginNudgeTitle": { + "message": "Save time with autofill" + }, + "newLoginNudgeBody": { + "message": "Include a Website so this login appears as an autofill suggestion." + }, + "newCardNudgeTitle": { + "message": "Seamless online checkout" + }, + "newCardNudgeBody": { + "message": "With cards, easily autofill payment forms securely and accurately." + }, + "newIdentityNudgeTitle": { + "message": "Simplify creating accounts" + }, + "newIdentityNudgeBody": { + "message": "With identities, quickly autofill long registration or contact forms." + }, + "newNoteNudgeTitle": { + "message": "Keep your sensitive data safe" + }, + "newNoteNudgeBody": { + "message": "With notes, securely store sensitive data like banking or insurance details." + }, + "newSshNudgeTitle": { + "message": "Developer-friendly SSH access" + }, + "newSshNudgeBody": { + "message": "Store your keys and connect with the SSH agent for fast, encrypted authentication." } } diff --git a/apps/desktop/src/locales/hr/messages.json b/apps/desktop/src/locales/hr/messages.json index 240f7406db1..af78a035927 100644 --- a/apps/desktop/src/locales/hr/messages.json +++ b/apps/desktop/src/locales/hr/messages.json @@ -393,6 +393,64 @@ "authenticatorKeyTotp": { "message": "Ključ autentifikatora (TOTP)" }, + "authenticatorKey": { + "message": "Authenticator key" + }, + "autofillOptions": { + "message": "Autofill options" + }, + "websiteUri": { + "message": "Website (URI)" + }, + "websiteUriCount": { + "message": "Website (URI) $COUNT$", + "description": "Label for an input field that contains a website URI. The input field is part of a list of fields, and the count indicates the position of the field in the list.", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + } + } + }, + "websiteAdded": { + "message": "Website added" + }, + "addWebsite": { + "message": "Add website" + }, + "deleteWebsite": { + "message": "Delete website" + }, + "owner": { + "message": "Owner" + }, + "addField": { + "message": "Add field" + }, + "fieldType": { + "message": "Field type" + }, + "fieldLabel": { + "message": "Field label" + }, + "add": { + "message": "Add" + }, + "textHelpText": { + "message": "Use text fields for data like security questions" + }, + "hiddenHelpText": { + "message": "Use hidden fields for sensitive data like a password" + }, + "checkBoxHelpText": { + "message": "Use checkboxes if you'd like to autofill a form's checkbox, like a remember email" + }, + "linkedHelpText": { + "message": "Use a linked field when you are experiencing autofill issues for a specific website." + }, + "linkedLabelHelpText": { + "message": "Enter the the field's html id, name, aria-label, or placeholder." + }, "folder": { "message": "Mapa" }, @@ -418,6 +476,9 @@ "message": "Povezano", "description": "This describes a field that is 'linked' (related) to another field." }, + "cfTypeCheckbox": { + "message": "Checkbox" + }, "linkedValue": { "message": "Povezana vrijednost", "description": "This describes a value that is 'linked' (related) to another value." @@ -1915,6 +1976,43 @@ } } }, + "cardDetails": { + "message": "Card details" + }, + "cardBrandDetails": { + "message": "$BRAND$ details", + "placeholders": { + "brand": { + "content": "$1", + "example": "Visa" + } + } + }, + "learnMoreAboutAuthenticators": { + "message": "Learn more about authenticators" + }, + "copyTOTP": { + "message": "Copy Authenticator key (TOTP)" + }, + "totpHelperTitle": { + "message": "Make 2-step verification seamless" + }, + "totpHelper": { + "message": "Bitwarden can store and fill 2-step verification codes. Copy and paste the key into this field." + }, + "totpHelperWithCapture": { + "message": "Bitwarden can store and fill 2-step verification codes. Select the camera icon to take a screenshot of this website's authenticator QR code, or copy and paste the key into this field." + }, + "premium": { + "message": "Premium", + "description": "Premium membership" + }, + "cardExpiredTitle": { + "message": "Expired card" + }, + "cardExpiredMessage": { + "message": "If you've renewed it, update the card's information" + }, "verificationRequired": { "message": "Potrebna je potvrda", "description": "Default title for the user verification dialog." @@ -2084,6 +2182,15 @@ "personalOwnershipPolicyInEffectImports": { "message": "Organizacijsko pravilo onemogućuje uvoz stavki u tvoj osobni trezor." }, + "personalDetails": { + "message": "Personal details" + }, + "identification": { + "message": "Identification" + }, + "contactInfo": { + "message": "Contact information" + }, "allSends": { "message": "Svi Sendovi", "description": "'Sends' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." @@ -2518,6 +2625,9 @@ "generateEmail": { "message": "Generiraj e-poštu" }, + "usernameGenerator": { + "message": "Username generator" + }, "spinboxBoundariesHint": { "message": "Vrijednost mora biti u rasponu $MIN$ - $MAX$.", "description": "Explains spin box minimum and maximum values to the user", @@ -3212,6 +3322,9 @@ "followTheStepsBelowToFinishLoggingIn": { "message": "Prati korake za dovršetak prijave." }, + "followTheStepsBelowToFinishLoggingInWithSecurityKey": { + "message": "Follow the steps below to finish logging in with your security key." + }, "launchDuo": { "message": "Pokreni Duo u pregledniku" }, @@ -3436,6 +3549,17 @@ "ssoError": { "message": "Nisu nađeni slobodni portovi za SSO prijavu." }, + "securePasswordGenerated": { + "message": "Secure password generated! Don't forget to also update your password on the website." + }, + "useGeneratorHelpTextPartOne": { + "message": "Use the generator", + "description": "This will be used as part of a larger sentence, broken up to include the generator icon. The full sentence will read 'Use the generator [GENERATOR_ICON] to create a strong unique password'" + }, + "useGeneratorHelpTextPartTwo": { + "message": "to create a strong unique password", + "description": "This will be used as part of a larger sentence, broken up to include the generator icon. The full sentence will read 'Use the generator [GENERATOR_ICON] to create a strong unique password'" + }, "biometricsStatusHelptextUnlockNeeded": { "message": "Biometrijsko otključavanje nije dostupno jer je prvo potrebno otključati PIN-om ili lozinkom." }, @@ -3514,6 +3638,27 @@ "setupTwoStepLogin": { "message": "Postavi dvostruku autentifikaciju" }, + "itemDetails": { + "message": "Item details" + }, + "itemName": { + "message": "Item name" + }, + "loginCredentials": { + "message": "Login credentials" + }, + "additionalOptions": { + "message": "Additional options" + }, + "itemHistory": { + "message": "Item history" + }, + "lastEdited": { + "message": "Last edited" + }, + "upload": { + "message": "Upload" + }, "newDeviceVerificationNoticeContentPage1": { "message": "Bitwarden će, počevši od veljače 2025., za provjeru prijava s novih uređaja poslati kôd na e-poštu tvog računa." }, @@ -3567,5 +3712,35 @@ }, "move": { "message": "Move" + }, + "newLoginNudgeTitle": { + "message": "Save time with autofill" + }, + "newLoginNudgeBody": { + "message": "Include a Website so this login appears as an autofill suggestion." + }, + "newCardNudgeTitle": { + "message": "Seamless online checkout" + }, + "newCardNudgeBody": { + "message": "With cards, easily autofill payment forms securely and accurately." + }, + "newIdentityNudgeTitle": { + "message": "Simplify creating accounts" + }, + "newIdentityNudgeBody": { + "message": "With identities, quickly autofill long registration or contact forms." + }, + "newNoteNudgeTitle": { + "message": "Keep your sensitive data safe" + }, + "newNoteNudgeBody": { + "message": "With notes, securely store sensitive data like banking or insurance details." + }, + "newSshNudgeTitle": { + "message": "Developer-friendly SSH access" + }, + "newSshNudgeBody": { + "message": "Store your keys and connect with the SSH agent for fast, encrypted authentication." } } diff --git a/apps/desktop/src/locales/hu/messages.json b/apps/desktop/src/locales/hu/messages.json index e2caf838a66..ae97ccd49e6 100644 --- a/apps/desktop/src/locales/hu/messages.json +++ b/apps/desktop/src/locales/hu/messages.json @@ -393,6 +393,64 @@ "authenticatorKeyTotp": { "message": "Hitelesítő kulcs (egyszeri-idő alapú)" }, + "authenticatorKey": { + "message": "Hitelesítő kulcs" + }, + "autofillOptions": { + "message": "Automatikus kitöltés opciók" + }, + "websiteUri": { + "message": "Webhely (URI)" + }, + "websiteUriCount": { + "message": "Webhely (URI) $COUNT$", + "description": "Label for an input field that contains a website URI. The input field is part of a list of fields, and the count indicates the position of the field in the list.", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + } + } + }, + "websiteAdded": { + "message": "A webhely hozzáadásra került." + }, + "addWebsite": { + "message": "Webhely hozzáadása" + }, + "deleteWebsite": { + "message": "Webhely törlése" + }, + "owner": { + "message": "Tulajdonos" + }, + "addField": { + "message": "Mező hozzáadása" + }, + "fieldType": { + "message": "Mezőtípus" + }, + "fieldLabel": { + "message": "Mezőfelirat" + }, + "add": { + "message": "Hozzáadás" + }, + "textHelpText": { + "message": "Szövegmezők használata olyan adatokhoz mint a biztonsági kérdések" + }, + "hiddenHelpText": { + "message": "Rejtett mezők használata olyan érzékeny adatokhoz mint a jelszó" + }, + "checkBoxHelpText": { + "message": "Jelölődobozok használata, ha automatikusan ki szeretnénk tölteni olyan űrlap jelölődobozt mint az email cím megjegyzése" + }, + "linkedHelpText": { + "message": "Csatolt mező használata, ha egy adott webhely automatikus kitöltésével kapcsolatos problémákat tapasztalunk." + }, + "linkedLabelHelpText": { + "message": "Adjuk meg a mező html azonosítóját, nevét, aria címkéjét vagy helyőrét." + }, "folder": { "message": "Mappa" }, @@ -418,6 +476,9 @@ "message": "Csatolva", "description": "This describes a field that is 'linked' (related) to another field." }, + "cfTypeCheckbox": { + "message": "Jelölődoboz" + }, "linkedValue": { "message": "Csatolt érték", "description": "This describes a value that is 'linked' (related) to another value." @@ -1915,6 +1976,43 @@ } } }, + "cardDetails": { + "message": "Kártyaadatok" + }, + "cardBrandDetails": { + "message": "$BRAND$ adatok", + "placeholders": { + "brand": { + "content": "$1", + "example": "Visa" + } + } + }, + "learnMoreAboutAuthenticators": { + "message": "További információ a hitelesítőkről" + }, + "copyTOTP": { + "message": "Hitelesítő kód másolása (TOTP)" + }, + "totpHelperTitle": { + "message": "Tegyük zökkenőmentessé a kétlépcsős azonosítást." + }, + "totpHelper": { + "message": "A Bitwarden képes tárolni és kitölteni a kétlépcsős ellenőrző kódokat. Másoljuk ki és illesszük be a kulcsot ebbe a mezőbe." + }, + "totpHelperWithCapture": { + "message": "A Bitwarden képes tárolni és kitölteni a kétlépcsős ellenőrző kódokat. Válasszuk a kamera ikont, hogy képernyőképet készítsünk a webhely hitelesítő QR kódjáról vagy másoljuk ki és illesszük be a kulcsot ebbe a mezőbe." + }, + "premium": { + "message": "Prémium", + "description": "Premium membership" + }, + "cardExpiredTitle": { + "message": "Lejárt kártya" + }, + "cardExpiredMessage": { + "message": "Ha megújítottuk, frissítsük a kártya adatait." + }, "verificationRequired": { "message": "Ellenőrzés szükséges", "description": "Default title for the user verification dialog." @@ -2084,6 +2182,15 @@ "personalOwnershipPolicyInEffectImports": { "message": "A szervezeti politika blokkolta az elemek importálását az egyedi széfbe." }, + "personalDetails": { + "message": "Személyes adatok" + }, + "identification": { + "message": "Azonosítás" + }, + "contactInfo": { + "message": "Elérhetőségi adatok" + }, "allSends": { "message": "Összes küldés", "description": "'Sends' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." @@ -2518,6 +2625,9 @@ "generateEmail": { "message": "Email generálása" }, + "usernameGenerator": { + "message": "Felhasználónév generátor" + }, "spinboxBoundariesHint": { "message": "Az érték legyen $MIN$ és $MAX$ között.", "description": "Explains spin box minimum and maximum values to the user", @@ -3212,6 +3322,9 @@ "followTheStepsBelowToFinishLoggingIn": { "message": "Kövessük az alábbi lépéseket a bejelentkezés befejezéséhez." }, + "followTheStepsBelowToFinishLoggingInWithSecurityKey": { + "message": "Kövessük az alábbi lépéseket a biztonsági kulccsal bejelentkezés befejezéséhez." + }, "launchDuo": { "message": "A Duo elindítása a böngészőben" }, @@ -3436,6 +3549,17 @@ "ssoError": { "message": "Nem található szabad port az sso bejelentkezéshez." }, + "securePasswordGenerated": { + "message": "A biztonságos jelszó generálásra került! Ne felejtsük el frissíteni a jelszót a webhelyen is." + }, + "useGeneratorHelpTextPartOne": { + "message": "Generátor használata", + "description": "This will be used as part of a larger sentence, broken up to include the generator icon. The full sentence will read 'Use the generator [GENERATOR_ICON] to create a strong unique password'" + }, + "useGeneratorHelpTextPartTwo": { + "message": "erős egyedi jelszó létrehozásához", + "description": "This will be used as part of a larger sentence, broken up to include the generator icon. The full sentence will read 'Use the generator [GENERATOR_ICON] to create a strong unique password'" + }, "biometricsStatusHelptextUnlockNeeded": { "message": "A biometrikus feloldás nem érhető el, mert először PIN kóddal vagy jelszóval kell feloldani." }, @@ -3514,6 +3638,27 @@ "setupTwoStepLogin": { "message": "Kétlépéses bejelentkezés beüzemelése" }, + "itemDetails": { + "message": "Elem részletek" + }, + "itemName": { + "message": "Elem neve" + }, + "loginCredentials": { + "message": "Bejelentkezési hitelesítések" + }, + "additionalOptions": { + "message": "Kiegészítő opciók" + }, + "itemHistory": { + "message": "Elem előzmény" + }, + "lastEdited": { + "message": "Utoljára szerkesztve" + }, + "upload": { + "message": "Feltöltés" + }, "newDeviceVerificationNoticeContentPage1": { "message": "A Bitwarden 2025 februárjától kódot küld a fiókhoz tartozó email-címre, amellyel ellenőrizhetők az új eszközökről történő bejelentkezések." }, @@ -3567,5 +3712,35 @@ }, "move": { "message": "Áthelyezés" + }, + "newLoginNudgeTitle": { + "message": "Idő megtakarítás automatikus kitöltéssel" + }, + "newLoginNudgeBody": { + "message": "Adjunk meg egy webhelyet, hogy ez a bejelentkezési név automatikusan kitöltendő javaslatként jelenjen meg." + }, + "newCardNudgeTitle": { + "message": "Zökkenőmentes online fizetés" + }, + "newCardNudgeBody": { + "message": "Kártyákkal könnyedén, biztonságosan és pontosan tölthetjük ki automatikusan a fizetési űrlapokat." + }, + "newIdentityNudgeTitle": { + "message": "Egyszerűsítsük a fiókok létrehozását" + }, + "newIdentityNudgeBody": { + "message": "Azonosítókkal gyorsan automatikusan kitölthetjük a hosszú regisztrációs vagy kapcsolatfelvételi űrlapokat." + }, + "newNoteNudgeTitle": { + "message": "Tartsuk biztonságban az érzékeny adatokat" + }, + "newNoteNudgeBody": { + "message": "Jegyzetekkel biztonságosan tárolhatjuk az érzékeny adatokat, például a banki vagy biztosítási adatokat." + }, + "newSshNudgeTitle": { + "message": "Fejlesztőbarát SSH hozzáférés" + }, + "newSshNudgeBody": { + "message": "Tároljuk a kulcsokat és csatlakozzunk az SSH-ügynökhöz a gyors, titkosított hitelesítéshez." } } diff --git a/apps/desktop/src/locales/id/messages.json b/apps/desktop/src/locales/id/messages.json index 4a95781d04e..e54bcece504 100644 --- a/apps/desktop/src/locales/id/messages.json +++ b/apps/desktop/src/locales/id/messages.json @@ -393,6 +393,64 @@ "authenticatorKeyTotp": { "message": "Kunci Autentikasi (TOTP)" }, + "authenticatorKey": { + "message": "Authenticator key" + }, + "autofillOptions": { + "message": "Autofill options" + }, + "websiteUri": { + "message": "Website (URI)" + }, + "websiteUriCount": { + "message": "Website (URI) $COUNT$", + "description": "Label for an input field that contains a website URI. The input field is part of a list of fields, and the count indicates the position of the field in the list.", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + } + } + }, + "websiteAdded": { + "message": "Website added" + }, + "addWebsite": { + "message": "Add website" + }, + "deleteWebsite": { + "message": "Delete website" + }, + "owner": { + "message": "Owner" + }, + "addField": { + "message": "Add field" + }, + "fieldType": { + "message": "Field type" + }, + "fieldLabel": { + "message": "Field label" + }, + "add": { + "message": "Add" + }, + "textHelpText": { + "message": "Use text fields for data like security questions" + }, + "hiddenHelpText": { + "message": "Use hidden fields for sensitive data like a password" + }, + "checkBoxHelpText": { + "message": "Use checkboxes if you'd like to autofill a form's checkbox, like a remember email" + }, + "linkedHelpText": { + "message": "Use a linked field when you are experiencing autofill issues for a specific website." + }, + "linkedLabelHelpText": { + "message": "Enter the the field's html id, name, aria-label, or placeholder." + }, "folder": { "message": "Folder" }, @@ -418,6 +476,9 @@ "message": "Terhubung", "description": "This describes a field that is 'linked' (related) to another field." }, + "cfTypeCheckbox": { + "message": "Checkbox" + }, "linkedValue": { "message": "Nilai terkait", "description": "This describes a value that is 'linked' (related) to another value." @@ -1915,6 +1976,43 @@ } } }, + "cardDetails": { + "message": "Card details" + }, + "cardBrandDetails": { + "message": "$BRAND$ details", + "placeholders": { + "brand": { + "content": "$1", + "example": "Visa" + } + } + }, + "learnMoreAboutAuthenticators": { + "message": "Learn more about authenticators" + }, + "copyTOTP": { + "message": "Copy Authenticator key (TOTP)" + }, + "totpHelperTitle": { + "message": "Make 2-step verification seamless" + }, + "totpHelper": { + "message": "Bitwarden can store and fill 2-step verification codes. Copy and paste the key into this field." + }, + "totpHelperWithCapture": { + "message": "Bitwarden can store and fill 2-step verification codes. Select the camera icon to take a screenshot of this website's authenticator QR code, or copy and paste the key into this field." + }, + "premium": { + "message": "Premium", + "description": "Premium membership" + }, + "cardExpiredTitle": { + "message": "Expired card" + }, + "cardExpiredMessage": { + "message": "If you've renewed it, update the card's information" + }, "verificationRequired": { "message": "Verification required", "description": "Default title for the user verification dialog." @@ -2084,6 +2182,15 @@ "personalOwnershipPolicyInEffectImports": { "message": "An organization policy has blocked importing items into your individual vault." }, + "personalDetails": { + "message": "Personal details" + }, + "identification": { + "message": "Identification" + }, + "contactInfo": { + "message": "Contact information" + }, "allSends": { "message": "Semua Sends", "description": "'Sends' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." @@ -2518,6 +2625,9 @@ "generateEmail": { "message": "Generate email" }, + "usernameGenerator": { + "message": "Username generator" + }, "spinboxBoundariesHint": { "message": "Value must be between $MIN$ and $MAX$.", "description": "Explains spin box minimum and maximum values to the user", @@ -3212,6 +3322,9 @@ "followTheStepsBelowToFinishLoggingIn": { "message": "Follow the steps below to finish logging in." }, + "followTheStepsBelowToFinishLoggingInWithSecurityKey": { + "message": "Follow the steps below to finish logging in with your security key." + }, "launchDuo": { "message": "Launch Duo in Browser" }, @@ -3436,6 +3549,17 @@ "ssoError": { "message": "No free ports could be found for the sso login." }, + "securePasswordGenerated": { + "message": "Secure password generated! Don't forget to also update your password on the website." + }, + "useGeneratorHelpTextPartOne": { + "message": "Use the generator", + "description": "This will be used as part of a larger sentence, broken up to include the generator icon. The full sentence will read 'Use the generator [GENERATOR_ICON] to create a strong unique password'" + }, + "useGeneratorHelpTextPartTwo": { + "message": "to create a strong unique password", + "description": "This will be used as part of a larger sentence, broken up to include the generator icon. The full sentence will read 'Use the generator [GENERATOR_ICON] to create a strong unique password'" + }, "biometricsStatusHelptextUnlockNeeded": { "message": "Biometric unlock is unavailable because PIN or password unlock is required first." }, @@ -3514,6 +3638,27 @@ "setupTwoStepLogin": { "message": "Set up two-step login" }, + "itemDetails": { + "message": "Item details" + }, + "itemName": { + "message": "Item name" + }, + "loginCredentials": { + "message": "Login credentials" + }, + "additionalOptions": { + "message": "Additional options" + }, + "itemHistory": { + "message": "Item history" + }, + "lastEdited": { + "message": "Last edited" + }, + "upload": { + "message": "Upload" + }, "newDeviceVerificationNoticeContentPage1": { "message": "Bitwarden will send a code to your account email to verify logins from new devices starting in February 2025." }, @@ -3567,5 +3712,35 @@ }, "move": { "message": "Move" + }, + "newLoginNudgeTitle": { + "message": "Save time with autofill" + }, + "newLoginNudgeBody": { + "message": "Include a Website so this login appears as an autofill suggestion." + }, + "newCardNudgeTitle": { + "message": "Seamless online checkout" + }, + "newCardNudgeBody": { + "message": "With cards, easily autofill payment forms securely and accurately." + }, + "newIdentityNudgeTitle": { + "message": "Simplify creating accounts" + }, + "newIdentityNudgeBody": { + "message": "With identities, quickly autofill long registration or contact forms." + }, + "newNoteNudgeTitle": { + "message": "Keep your sensitive data safe" + }, + "newNoteNudgeBody": { + "message": "With notes, securely store sensitive data like banking or insurance details." + }, + "newSshNudgeTitle": { + "message": "Developer-friendly SSH access" + }, + "newSshNudgeBody": { + "message": "Store your keys and connect with the SSH agent for fast, encrypted authentication." } } diff --git a/apps/desktop/src/locales/it/messages.json b/apps/desktop/src/locales/it/messages.json index 5376e974ae5..f02823d09de 100644 --- a/apps/desktop/src/locales/it/messages.json +++ b/apps/desktop/src/locales/it/messages.json @@ -393,6 +393,64 @@ "authenticatorKeyTotp": { "message": "Chiave di autenticazione (TOTP)" }, + "authenticatorKey": { + "message": "Authenticator key" + }, + "autofillOptions": { + "message": "Autofill options" + }, + "websiteUri": { + "message": "Website (URI)" + }, + "websiteUriCount": { + "message": "Website (URI) $COUNT$", + "description": "Label for an input field that contains a website URI. The input field is part of a list of fields, and the count indicates the position of the field in the list.", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + } + } + }, + "websiteAdded": { + "message": "Website added" + }, + "addWebsite": { + "message": "Add website" + }, + "deleteWebsite": { + "message": "Delete website" + }, + "owner": { + "message": "Owner" + }, + "addField": { + "message": "Add field" + }, + "fieldType": { + "message": "Field type" + }, + "fieldLabel": { + "message": "Field label" + }, + "add": { + "message": "Add" + }, + "textHelpText": { + "message": "Use text fields for data like security questions" + }, + "hiddenHelpText": { + "message": "Use hidden fields for sensitive data like a password" + }, + "checkBoxHelpText": { + "message": "Use checkboxes if you'd like to autofill a form's checkbox, like a remember email" + }, + "linkedHelpText": { + "message": "Use a linked field when you are experiencing autofill issues for a specific website." + }, + "linkedLabelHelpText": { + "message": "Enter the the field's html id, name, aria-label, or placeholder." + }, "folder": { "message": "Cartella" }, @@ -418,6 +476,9 @@ "message": "Collegato", "description": "This describes a field that is 'linked' (related) to another field." }, + "cfTypeCheckbox": { + "message": "Checkbox" + }, "linkedValue": { "message": "Valore collegato", "description": "This describes a value that is 'linked' (related) to another value." @@ -1915,6 +1976,43 @@ } } }, + "cardDetails": { + "message": "Card details" + }, + "cardBrandDetails": { + "message": "$BRAND$ details", + "placeholders": { + "brand": { + "content": "$1", + "example": "Visa" + } + } + }, + "learnMoreAboutAuthenticators": { + "message": "Learn more about authenticators" + }, + "copyTOTP": { + "message": "Copy Authenticator key (TOTP)" + }, + "totpHelperTitle": { + "message": "Make 2-step verification seamless" + }, + "totpHelper": { + "message": "Bitwarden can store and fill 2-step verification codes. Copy and paste the key into this field." + }, + "totpHelperWithCapture": { + "message": "Bitwarden can store and fill 2-step verification codes. Select the camera icon to take a screenshot of this website's authenticator QR code, or copy and paste the key into this field." + }, + "premium": { + "message": "Premium", + "description": "Premium membership" + }, + "cardExpiredTitle": { + "message": "Expired card" + }, + "cardExpiredMessage": { + "message": "If you've renewed it, update the card's information" + }, "verificationRequired": { "message": "Verifica necessaria", "description": "Default title for the user verification dialog." @@ -2084,6 +2182,15 @@ "personalOwnershipPolicyInEffectImports": { "message": "Una politica dell'organizzazione ti impedisce di importare elementi nella tua cassaforte individuale." }, + "personalDetails": { + "message": "Personal details" + }, + "identification": { + "message": "Identification" + }, + "contactInfo": { + "message": "Contact information" + }, "allSends": { "message": "Tutti i Send", "description": "'Sends' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." @@ -2518,6 +2625,9 @@ "generateEmail": { "message": "Genera e-mail" }, + "usernameGenerator": { + "message": "Username generator" + }, "spinboxBoundariesHint": { "message": "Il valore deve essere compreso tra $MIN$ e $MAX$.", "description": "Explains spin box minimum and maximum values to the user", @@ -3212,6 +3322,9 @@ "followTheStepsBelowToFinishLoggingIn": { "message": "Segui i passaggi qui sotto per completare l'accesso." }, + "followTheStepsBelowToFinishLoggingInWithSecurityKey": { + "message": "Follow the steps below to finish logging in with your security key." + }, "launchDuo": { "message": "Avvia Duo nel browser" }, @@ -3436,6 +3549,17 @@ "ssoError": { "message": "Non è stato possibile trovare nessuna porta libera per il login Sso." }, + "securePasswordGenerated": { + "message": "Secure password generated! Don't forget to also update your password on the website." + }, + "useGeneratorHelpTextPartOne": { + "message": "Use the generator", + "description": "This will be used as part of a larger sentence, broken up to include the generator icon. The full sentence will read 'Use the generator [GENERATOR_ICON] to create a strong unique password'" + }, + "useGeneratorHelpTextPartTwo": { + "message": "to create a strong unique password", + "description": "This will be used as part of a larger sentence, broken up to include the generator icon. The full sentence will read 'Use the generator [GENERATOR_ICON] to create a strong unique password'" + }, "biometricsStatusHelptextUnlockNeeded": { "message": "Lo sblocco biometrico non è disponibile perché è necessario prima sbloccare con PIN o parola d'accesso." }, @@ -3514,6 +3638,27 @@ "setupTwoStepLogin": { "message": "Imposta accesso in due passaggi" }, + "itemDetails": { + "message": "Item details" + }, + "itemName": { + "message": "Item name" + }, + "loginCredentials": { + "message": "Login credentials" + }, + "additionalOptions": { + "message": "Additional options" + }, + "itemHistory": { + "message": "Item history" + }, + "lastEdited": { + "message": "Last edited" + }, + "upload": { + "message": "Upload" + }, "newDeviceVerificationNoticeContentPage1": { "message": "Bitwarden invierà un codice all'e-mail del tuo account per verificare gli accessi da nuovi dispositivi a partire da febbraio 2025." }, @@ -3567,5 +3712,35 @@ }, "move": { "message": "Move" + }, + "newLoginNudgeTitle": { + "message": "Save time with autofill" + }, + "newLoginNudgeBody": { + "message": "Include a Website so this login appears as an autofill suggestion." + }, + "newCardNudgeTitle": { + "message": "Seamless online checkout" + }, + "newCardNudgeBody": { + "message": "With cards, easily autofill payment forms securely and accurately." + }, + "newIdentityNudgeTitle": { + "message": "Simplify creating accounts" + }, + "newIdentityNudgeBody": { + "message": "With identities, quickly autofill long registration or contact forms." + }, + "newNoteNudgeTitle": { + "message": "Keep your sensitive data safe" + }, + "newNoteNudgeBody": { + "message": "With notes, securely store sensitive data like banking or insurance details." + }, + "newSshNudgeTitle": { + "message": "Developer-friendly SSH access" + }, + "newSshNudgeBody": { + "message": "Store your keys and connect with the SSH agent for fast, encrypted authentication." } } diff --git a/apps/desktop/src/locales/ja/messages.json b/apps/desktop/src/locales/ja/messages.json index 5f9e7cbc6ab..dc87276cfe3 100644 --- a/apps/desktop/src/locales/ja/messages.json +++ b/apps/desktop/src/locales/ja/messages.json @@ -393,6 +393,64 @@ "authenticatorKeyTotp": { "message": "認証キー (TOTP)" }, + "authenticatorKey": { + "message": "Authenticator key" + }, + "autofillOptions": { + "message": "Autofill options" + }, + "websiteUri": { + "message": "Website (URI)" + }, + "websiteUriCount": { + "message": "Website (URI) $COUNT$", + "description": "Label for an input field that contains a website URI. The input field is part of a list of fields, and the count indicates the position of the field in the list.", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + } + } + }, + "websiteAdded": { + "message": "Website added" + }, + "addWebsite": { + "message": "Add website" + }, + "deleteWebsite": { + "message": "Delete website" + }, + "owner": { + "message": "Owner" + }, + "addField": { + "message": "Add field" + }, + "fieldType": { + "message": "Field type" + }, + "fieldLabel": { + "message": "Field label" + }, + "add": { + "message": "Add" + }, + "textHelpText": { + "message": "Use text fields for data like security questions" + }, + "hiddenHelpText": { + "message": "Use hidden fields for sensitive data like a password" + }, + "checkBoxHelpText": { + "message": "Use checkboxes if you'd like to autofill a form's checkbox, like a remember email" + }, + "linkedHelpText": { + "message": "Use a linked field when you are experiencing autofill issues for a specific website." + }, + "linkedLabelHelpText": { + "message": "Enter the the field's html id, name, aria-label, or placeholder." + }, "folder": { "message": "フォルダー" }, @@ -418,6 +476,9 @@ "message": "リンク済", "description": "This describes a field that is 'linked' (related) to another field." }, + "cfTypeCheckbox": { + "message": "Checkbox" + }, "linkedValue": { "message": "リンクされた値", "description": "This describes a value that is 'linked' (related) to another value." @@ -1915,6 +1976,43 @@ } } }, + "cardDetails": { + "message": "Card details" + }, + "cardBrandDetails": { + "message": "$BRAND$ details", + "placeholders": { + "brand": { + "content": "$1", + "example": "Visa" + } + } + }, + "learnMoreAboutAuthenticators": { + "message": "Learn more about authenticators" + }, + "copyTOTP": { + "message": "Copy Authenticator key (TOTP)" + }, + "totpHelperTitle": { + "message": "Make 2-step verification seamless" + }, + "totpHelper": { + "message": "Bitwarden can store and fill 2-step verification codes. Copy and paste the key into this field." + }, + "totpHelperWithCapture": { + "message": "Bitwarden can store and fill 2-step verification codes. Select the camera icon to take a screenshot of this website's authenticator QR code, or copy and paste the key into this field." + }, + "premium": { + "message": "Premium", + "description": "Premium membership" + }, + "cardExpiredTitle": { + "message": "Expired card" + }, + "cardExpiredMessage": { + "message": "If you've renewed it, update the card's information" + }, "verificationRequired": { "message": "認証が必要です", "description": "Default title for the user verification dialog." @@ -2084,6 +2182,15 @@ "personalOwnershipPolicyInEffectImports": { "message": "組織のポリシーにより、個々の保管庫へのアイテムのインポートがブロックされました。" }, + "personalDetails": { + "message": "Personal details" + }, + "identification": { + "message": "Identification" + }, + "contactInfo": { + "message": "Contact information" + }, "allSends": { "message": "すべての Send", "description": "'Sends' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." @@ -2518,6 +2625,9 @@ "generateEmail": { "message": "メールアドレスを生成" }, + "usernameGenerator": { + "message": "Username generator" + }, "spinboxBoundariesHint": { "message": "値は $MIN$ から $MAX$ の間でなければなりません。", "description": "Explains spin box minimum and maximum values to the user", @@ -3212,6 +3322,9 @@ "followTheStepsBelowToFinishLoggingIn": { "message": "以下の手順に従ってログインを完了してください。" }, + "followTheStepsBelowToFinishLoggingInWithSecurityKey": { + "message": "Follow the steps below to finish logging in with your security key." + }, "launchDuo": { "message": "ブラウザで Duo を起動" }, @@ -3436,6 +3549,17 @@ "ssoError": { "message": "SSO ログインのための空きポートが見つかりませんでした。" }, + "securePasswordGenerated": { + "message": "Secure password generated! Don't forget to also update your password on the website." + }, + "useGeneratorHelpTextPartOne": { + "message": "Use the generator", + "description": "This will be used as part of a larger sentence, broken up to include the generator icon. The full sentence will read 'Use the generator [GENERATOR_ICON] to create a strong unique password'" + }, + "useGeneratorHelpTextPartTwo": { + "message": "to create a strong unique password", + "description": "This will be used as part of a larger sentence, broken up to include the generator icon. The full sentence will read 'Use the generator [GENERATOR_ICON] to create a strong unique password'" + }, "biometricsStatusHelptextUnlockNeeded": { "message": "PINまたはパスワードによるロック解除が最初に必要なため、生体認証によるロック解除は利用できません。" }, @@ -3514,6 +3638,27 @@ "setupTwoStepLogin": { "message": "2段階認証によるログインを設定する" }, + "itemDetails": { + "message": "Item details" + }, + "itemName": { + "message": "Item name" + }, + "loginCredentials": { + "message": "Login credentials" + }, + "additionalOptions": { + "message": "Additional options" + }, + "itemHistory": { + "message": "Item history" + }, + "lastEdited": { + "message": "Last edited" + }, + "upload": { + "message": "Upload" + }, "newDeviceVerificationNoticeContentPage1": { "message": "Bitwarden は2025年2月以降、新しいデバイスからのログイン時にアカウントのメールアドレスに確認コードを送信します。" }, @@ -3567,5 +3712,35 @@ }, "move": { "message": "移動" + }, + "newLoginNudgeTitle": { + "message": "Save time with autofill" + }, + "newLoginNudgeBody": { + "message": "Include a Website so this login appears as an autofill suggestion." + }, + "newCardNudgeTitle": { + "message": "Seamless online checkout" + }, + "newCardNudgeBody": { + "message": "With cards, easily autofill payment forms securely and accurately." + }, + "newIdentityNudgeTitle": { + "message": "Simplify creating accounts" + }, + "newIdentityNudgeBody": { + "message": "With identities, quickly autofill long registration or contact forms." + }, + "newNoteNudgeTitle": { + "message": "Keep your sensitive data safe" + }, + "newNoteNudgeBody": { + "message": "With notes, securely store sensitive data like banking or insurance details." + }, + "newSshNudgeTitle": { + "message": "Developer-friendly SSH access" + }, + "newSshNudgeBody": { + "message": "Store your keys and connect with the SSH agent for fast, encrypted authentication." } } diff --git a/apps/desktop/src/locales/ka/messages.json b/apps/desktop/src/locales/ka/messages.json index 6e3101404e0..dd01fa84df2 100644 --- a/apps/desktop/src/locales/ka/messages.json +++ b/apps/desktop/src/locales/ka/messages.json @@ -393,6 +393,64 @@ "authenticatorKeyTotp": { "message": "Authenticator key (TOTP)" }, + "authenticatorKey": { + "message": "Authenticator key" + }, + "autofillOptions": { + "message": "Autofill options" + }, + "websiteUri": { + "message": "Website (URI)" + }, + "websiteUriCount": { + "message": "Website (URI) $COUNT$", + "description": "Label for an input field that contains a website URI. The input field is part of a list of fields, and the count indicates the position of the field in the list.", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + } + } + }, + "websiteAdded": { + "message": "Website added" + }, + "addWebsite": { + "message": "Add website" + }, + "deleteWebsite": { + "message": "Delete website" + }, + "owner": { + "message": "Owner" + }, + "addField": { + "message": "Add field" + }, + "fieldType": { + "message": "Field type" + }, + "fieldLabel": { + "message": "Field label" + }, + "add": { + "message": "Add" + }, + "textHelpText": { + "message": "Use text fields for data like security questions" + }, + "hiddenHelpText": { + "message": "Use hidden fields for sensitive data like a password" + }, + "checkBoxHelpText": { + "message": "Use checkboxes if you'd like to autofill a form's checkbox, like a remember email" + }, + "linkedHelpText": { + "message": "Use a linked field when you are experiencing autofill issues for a specific website." + }, + "linkedLabelHelpText": { + "message": "Enter the the field's html id, name, aria-label, or placeholder." + }, "folder": { "message": "საქაღალდე" }, @@ -418,6 +476,9 @@ "message": "მიბმული", "description": "This describes a field that is 'linked' (related) to another field." }, + "cfTypeCheckbox": { + "message": "Checkbox" + }, "linkedValue": { "message": "Linked value", "description": "This describes a value that is 'linked' (related) to another value." @@ -1915,6 +1976,43 @@ } } }, + "cardDetails": { + "message": "Card details" + }, + "cardBrandDetails": { + "message": "$BRAND$ details", + "placeholders": { + "brand": { + "content": "$1", + "example": "Visa" + } + } + }, + "learnMoreAboutAuthenticators": { + "message": "Learn more about authenticators" + }, + "copyTOTP": { + "message": "Copy Authenticator key (TOTP)" + }, + "totpHelperTitle": { + "message": "Make 2-step verification seamless" + }, + "totpHelper": { + "message": "Bitwarden can store and fill 2-step verification codes. Copy and paste the key into this field." + }, + "totpHelperWithCapture": { + "message": "Bitwarden can store and fill 2-step verification codes. Select the camera icon to take a screenshot of this website's authenticator QR code, or copy and paste the key into this field." + }, + "premium": { + "message": "Premium", + "description": "Premium membership" + }, + "cardExpiredTitle": { + "message": "Expired card" + }, + "cardExpiredMessage": { + "message": "If you've renewed it, update the card's information" + }, "verificationRequired": { "message": "Verification required", "description": "Default title for the user verification dialog." @@ -2084,6 +2182,15 @@ "personalOwnershipPolicyInEffectImports": { "message": "An organization policy has blocked importing items into your individual vault." }, + "personalDetails": { + "message": "Personal details" + }, + "identification": { + "message": "Identification" + }, + "contactInfo": { + "message": "Contact information" + }, "allSends": { "message": "All Sends", "description": "'Sends' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." @@ -2518,6 +2625,9 @@ "generateEmail": { "message": "Generate email" }, + "usernameGenerator": { + "message": "Username generator" + }, "spinboxBoundariesHint": { "message": "Value must be between $MIN$ and $MAX$.", "description": "Explains spin box minimum and maximum values to the user", @@ -3212,6 +3322,9 @@ "followTheStepsBelowToFinishLoggingIn": { "message": "Follow the steps below to finish logging in." }, + "followTheStepsBelowToFinishLoggingInWithSecurityKey": { + "message": "Follow the steps below to finish logging in with your security key." + }, "launchDuo": { "message": "Launch Duo in Browser" }, @@ -3436,6 +3549,17 @@ "ssoError": { "message": "No free ports could be found for the sso login." }, + "securePasswordGenerated": { + "message": "Secure password generated! Don't forget to also update your password on the website." + }, + "useGeneratorHelpTextPartOne": { + "message": "Use the generator", + "description": "This will be used as part of a larger sentence, broken up to include the generator icon. The full sentence will read 'Use the generator [GENERATOR_ICON] to create a strong unique password'" + }, + "useGeneratorHelpTextPartTwo": { + "message": "to create a strong unique password", + "description": "This will be used as part of a larger sentence, broken up to include the generator icon. The full sentence will read 'Use the generator [GENERATOR_ICON] to create a strong unique password'" + }, "biometricsStatusHelptextUnlockNeeded": { "message": "Biometric unlock is unavailable because PIN or password unlock is required first." }, @@ -3514,6 +3638,27 @@ "setupTwoStepLogin": { "message": "Set up two-step login" }, + "itemDetails": { + "message": "Item details" + }, + "itemName": { + "message": "Item name" + }, + "loginCredentials": { + "message": "Login credentials" + }, + "additionalOptions": { + "message": "Additional options" + }, + "itemHistory": { + "message": "Item history" + }, + "lastEdited": { + "message": "Last edited" + }, + "upload": { + "message": "Upload" + }, "newDeviceVerificationNoticeContentPage1": { "message": "Bitwarden will send a code to your account email to verify logins from new devices starting in February 2025." }, @@ -3567,5 +3712,35 @@ }, "move": { "message": "Move" + }, + "newLoginNudgeTitle": { + "message": "Save time with autofill" + }, + "newLoginNudgeBody": { + "message": "Include a Website so this login appears as an autofill suggestion." + }, + "newCardNudgeTitle": { + "message": "Seamless online checkout" + }, + "newCardNudgeBody": { + "message": "With cards, easily autofill payment forms securely and accurately." + }, + "newIdentityNudgeTitle": { + "message": "Simplify creating accounts" + }, + "newIdentityNudgeBody": { + "message": "With identities, quickly autofill long registration or contact forms." + }, + "newNoteNudgeTitle": { + "message": "Keep your sensitive data safe" + }, + "newNoteNudgeBody": { + "message": "With notes, securely store sensitive data like banking or insurance details." + }, + "newSshNudgeTitle": { + "message": "Developer-friendly SSH access" + }, + "newSshNudgeBody": { + "message": "Store your keys and connect with the SSH agent for fast, encrypted authentication." } } diff --git a/apps/desktop/src/locales/km/messages.json b/apps/desktop/src/locales/km/messages.json index 8850cbe5a3f..2350e0df4c7 100644 --- a/apps/desktop/src/locales/km/messages.json +++ b/apps/desktop/src/locales/km/messages.json @@ -393,6 +393,64 @@ "authenticatorKeyTotp": { "message": "Authenticator key (TOTP)" }, + "authenticatorKey": { + "message": "Authenticator key" + }, + "autofillOptions": { + "message": "Autofill options" + }, + "websiteUri": { + "message": "Website (URI)" + }, + "websiteUriCount": { + "message": "Website (URI) $COUNT$", + "description": "Label for an input field that contains a website URI. The input field is part of a list of fields, and the count indicates the position of the field in the list.", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + } + } + }, + "websiteAdded": { + "message": "Website added" + }, + "addWebsite": { + "message": "Add website" + }, + "deleteWebsite": { + "message": "Delete website" + }, + "owner": { + "message": "Owner" + }, + "addField": { + "message": "Add field" + }, + "fieldType": { + "message": "Field type" + }, + "fieldLabel": { + "message": "Field label" + }, + "add": { + "message": "Add" + }, + "textHelpText": { + "message": "Use text fields for data like security questions" + }, + "hiddenHelpText": { + "message": "Use hidden fields for sensitive data like a password" + }, + "checkBoxHelpText": { + "message": "Use checkboxes if you'd like to autofill a form's checkbox, like a remember email" + }, + "linkedHelpText": { + "message": "Use a linked field when you are experiencing autofill issues for a specific website." + }, + "linkedLabelHelpText": { + "message": "Enter the the field's html id, name, aria-label, or placeholder." + }, "folder": { "message": "Folder" }, @@ -418,6 +476,9 @@ "message": "Linked", "description": "This describes a field that is 'linked' (related) to another field." }, + "cfTypeCheckbox": { + "message": "Checkbox" + }, "linkedValue": { "message": "Linked value", "description": "This describes a value that is 'linked' (related) to another value." @@ -1915,6 +1976,43 @@ } } }, + "cardDetails": { + "message": "Card details" + }, + "cardBrandDetails": { + "message": "$BRAND$ details", + "placeholders": { + "brand": { + "content": "$1", + "example": "Visa" + } + } + }, + "learnMoreAboutAuthenticators": { + "message": "Learn more about authenticators" + }, + "copyTOTP": { + "message": "Copy Authenticator key (TOTP)" + }, + "totpHelperTitle": { + "message": "Make 2-step verification seamless" + }, + "totpHelper": { + "message": "Bitwarden can store and fill 2-step verification codes. Copy and paste the key into this field." + }, + "totpHelperWithCapture": { + "message": "Bitwarden can store and fill 2-step verification codes. Select the camera icon to take a screenshot of this website's authenticator QR code, or copy and paste the key into this field." + }, + "premium": { + "message": "Premium", + "description": "Premium membership" + }, + "cardExpiredTitle": { + "message": "Expired card" + }, + "cardExpiredMessage": { + "message": "If you've renewed it, update the card's information" + }, "verificationRequired": { "message": "Verification required", "description": "Default title for the user verification dialog." @@ -2084,6 +2182,15 @@ "personalOwnershipPolicyInEffectImports": { "message": "An organization policy has blocked importing items into your individual vault." }, + "personalDetails": { + "message": "Personal details" + }, + "identification": { + "message": "Identification" + }, + "contactInfo": { + "message": "Contact information" + }, "allSends": { "message": "All Sends", "description": "'Sends' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." @@ -2518,6 +2625,9 @@ "generateEmail": { "message": "Generate email" }, + "usernameGenerator": { + "message": "Username generator" + }, "spinboxBoundariesHint": { "message": "Value must be between $MIN$ and $MAX$.", "description": "Explains spin box minimum and maximum values to the user", @@ -3212,6 +3322,9 @@ "followTheStepsBelowToFinishLoggingIn": { "message": "Follow the steps below to finish logging in." }, + "followTheStepsBelowToFinishLoggingInWithSecurityKey": { + "message": "Follow the steps below to finish logging in with your security key." + }, "launchDuo": { "message": "Launch Duo in Browser" }, @@ -3436,6 +3549,17 @@ "ssoError": { "message": "No free ports could be found for the sso login." }, + "securePasswordGenerated": { + "message": "Secure password generated! Don't forget to also update your password on the website." + }, + "useGeneratorHelpTextPartOne": { + "message": "Use the generator", + "description": "This will be used as part of a larger sentence, broken up to include the generator icon. The full sentence will read 'Use the generator [GENERATOR_ICON] to create a strong unique password'" + }, + "useGeneratorHelpTextPartTwo": { + "message": "to create a strong unique password", + "description": "This will be used as part of a larger sentence, broken up to include the generator icon. The full sentence will read 'Use the generator [GENERATOR_ICON] to create a strong unique password'" + }, "biometricsStatusHelptextUnlockNeeded": { "message": "Biometric unlock is unavailable because PIN or password unlock is required first." }, @@ -3514,6 +3638,27 @@ "setupTwoStepLogin": { "message": "Set up two-step login" }, + "itemDetails": { + "message": "Item details" + }, + "itemName": { + "message": "Item name" + }, + "loginCredentials": { + "message": "Login credentials" + }, + "additionalOptions": { + "message": "Additional options" + }, + "itemHistory": { + "message": "Item history" + }, + "lastEdited": { + "message": "Last edited" + }, + "upload": { + "message": "Upload" + }, "newDeviceVerificationNoticeContentPage1": { "message": "Bitwarden will send a code to your account email to verify logins from new devices starting in February 2025." }, @@ -3567,5 +3712,35 @@ }, "move": { "message": "Move" + }, + "newLoginNudgeTitle": { + "message": "Save time with autofill" + }, + "newLoginNudgeBody": { + "message": "Include a Website so this login appears as an autofill suggestion." + }, + "newCardNudgeTitle": { + "message": "Seamless online checkout" + }, + "newCardNudgeBody": { + "message": "With cards, easily autofill payment forms securely and accurately." + }, + "newIdentityNudgeTitle": { + "message": "Simplify creating accounts" + }, + "newIdentityNudgeBody": { + "message": "With identities, quickly autofill long registration or contact forms." + }, + "newNoteNudgeTitle": { + "message": "Keep your sensitive data safe" + }, + "newNoteNudgeBody": { + "message": "With notes, securely store sensitive data like banking or insurance details." + }, + "newSshNudgeTitle": { + "message": "Developer-friendly SSH access" + }, + "newSshNudgeBody": { + "message": "Store your keys and connect with the SSH agent for fast, encrypted authentication." } } diff --git a/apps/desktop/src/locales/kn/messages.json b/apps/desktop/src/locales/kn/messages.json index 6dc91ad0ca8..b0f9ac4849f 100644 --- a/apps/desktop/src/locales/kn/messages.json +++ b/apps/desktop/src/locales/kn/messages.json @@ -393,6 +393,64 @@ "authenticatorKeyTotp": { "message": "ದೃಢೀಕರಣ ಕೀ (TOTP)" }, + "authenticatorKey": { + "message": "Authenticator key" + }, + "autofillOptions": { + "message": "Autofill options" + }, + "websiteUri": { + "message": "Website (URI)" + }, + "websiteUriCount": { + "message": "Website (URI) $COUNT$", + "description": "Label for an input field that contains a website URI. The input field is part of a list of fields, and the count indicates the position of the field in the list.", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + } + } + }, + "websiteAdded": { + "message": "Website added" + }, + "addWebsite": { + "message": "Add website" + }, + "deleteWebsite": { + "message": "Delete website" + }, + "owner": { + "message": "Owner" + }, + "addField": { + "message": "Add field" + }, + "fieldType": { + "message": "Field type" + }, + "fieldLabel": { + "message": "Field label" + }, + "add": { + "message": "Add" + }, + "textHelpText": { + "message": "Use text fields for data like security questions" + }, + "hiddenHelpText": { + "message": "Use hidden fields for sensitive data like a password" + }, + "checkBoxHelpText": { + "message": "Use checkboxes if you'd like to autofill a form's checkbox, like a remember email" + }, + "linkedHelpText": { + "message": "Use a linked field when you are experiencing autofill issues for a specific website." + }, + "linkedLabelHelpText": { + "message": "Enter the the field's html id, name, aria-label, or placeholder." + }, "folder": { "message": "ಫೋಲ್ಡರ್" }, @@ -418,6 +476,9 @@ "message": "Linked", "description": "This describes a field that is 'linked' (related) to another field." }, + "cfTypeCheckbox": { + "message": "Checkbox" + }, "linkedValue": { "message": "Linked value", "description": "This describes a value that is 'linked' (related) to another value." @@ -1915,6 +1976,43 @@ } } }, + "cardDetails": { + "message": "Card details" + }, + "cardBrandDetails": { + "message": "$BRAND$ details", + "placeholders": { + "brand": { + "content": "$1", + "example": "Visa" + } + } + }, + "learnMoreAboutAuthenticators": { + "message": "Learn more about authenticators" + }, + "copyTOTP": { + "message": "Copy Authenticator key (TOTP)" + }, + "totpHelperTitle": { + "message": "Make 2-step verification seamless" + }, + "totpHelper": { + "message": "Bitwarden can store and fill 2-step verification codes. Copy and paste the key into this field." + }, + "totpHelperWithCapture": { + "message": "Bitwarden can store and fill 2-step verification codes. Select the camera icon to take a screenshot of this website's authenticator QR code, or copy and paste the key into this field." + }, + "premium": { + "message": "Premium", + "description": "Premium membership" + }, + "cardExpiredTitle": { + "message": "Expired card" + }, + "cardExpiredMessage": { + "message": "If you've renewed it, update the card's information" + }, "verificationRequired": { "message": "Verification required", "description": "Default title for the user verification dialog." @@ -2084,6 +2182,15 @@ "personalOwnershipPolicyInEffectImports": { "message": "An organization policy has blocked importing items into your individual vault." }, + "personalDetails": { + "message": "Personal details" + }, + "identification": { + "message": "Identification" + }, + "contactInfo": { + "message": "Contact information" + }, "allSends": { "message": "ಎಲ್ಲಾ ಕಳುಹಿಸುತ್ತದೆ", "description": "'Sends' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." @@ -2518,6 +2625,9 @@ "generateEmail": { "message": "Generate email" }, + "usernameGenerator": { + "message": "Username generator" + }, "spinboxBoundariesHint": { "message": "Value must be between $MIN$ and $MAX$.", "description": "Explains spin box minimum and maximum values to the user", @@ -3212,6 +3322,9 @@ "followTheStepsBelowToFinishLoggingIn": { "message": "Follow the steps below to finish logging in." }, + "followTheStepsBelowToFinishLoggingInWithSecurityKey": { + "message": "Follow the steps below to finish logging in with your security key." + }, "launchDuo": { "message": "Launch Duo in Browser" }, @@ -3436,6 +3549,17 @@ "ssoError": { "message": "No free ports could be found for the sso login." }, + "securePasswordGenerated": { + "message": "Secure password generated! Don't forget to also update your password on the website." + }, + "useGeneratorHelpTextPartOne": { + "message": "Use the generator", + "description": "This will be used as part of a larger sentence, broken up to include the generator icon. The full sentence will read 'Use the generator [GENERATOR_ICON] to create a strong unique password'" + }, + "useGeneratorHelpTextPartTwo": { + "message": "to create a strong unique password", + "description": "This will be used as part of a larger sentence, broken up to include the generator icon. The full sentence will read 'Use the generator [GENERATOR_ICON] to create a strong unique password'" + }, "biometricsStatusHelptextUnlockNeeded": { "message": "Biometric unlock is unavailable because PIN or password unlock is required first." }, @@ -3514,6 +3638,27 @@ "setupTwoStepLogin": { "message": "Set up two-step login" }, + "itemDetails": { + "message": "Item details" + }, + "itemName": { + "message": "Item name" + }, + "loginCredentials": { + "message": "Login credentials" + }, + "additionalOptions": { + "message": "Additional options" + }, + "itemHistory": { + "message": "Item history" + }, + "lastEdited": { + "message": "Last edited" + }, + "upload": { + "message": "Upload" + }, "newDeviceVerificationNoticeContentPage1": { "message": "Bitwarden will send a code to your account email to verify logins from new devices starting in February 2025." }, @@ -3567,5 +3712,35 @@ }, "move": { "message": "Move" + }, + "newLoginNudgeTitle": { + "message": "Save time with autofill" + }, + "newLoginNudgeBody": { + "message": "Include a Website so this login appears as an autofill suggestion." + }, + "newCardNudgeTitle": { + "message": "Seamless online checkout" + }, + "newCardNudgeBody": { + "message": "With cards, easily autofill payment forms securely and accurately." + }, + "newIdentityNudgeTitle": { + "message": "Simplify creating accounts" + }, + "newIdentityNudgeBody": { + "message": "With identities, quickly autofill long registration or contact forms." + }, + "newNoteNudgeTitle": { + "message": "Keep your sensitive data safe" + }, + "newNoteNudgeBody": { + "message": "With notes, securely store sensitive data like banking or insurance details." + }, + "newSshNudgeTitle": { + "message": "Developer-friendly SSH access" + }, + "newSshNudgeBody": { + "message": "Store your keys and connect with the SSH agent for fast, encrypted authentication." } } diff --git a/apps/desktop/src/locales/ko/messages.json b/apps/desktop/src/locales/ko/messages.json index d684a8ed405..ed94aae47e8 100644 --- a/apps/desktop/src/locales/ko/messages.json +++ b/apps/desktop/src/locales/ko/messages.json @@ -393,6 +393,64 @@ "authenticatorKeyTotp": { "message": "인증 키 (TOTP)" }, + "authenticatorKey": { + "message": "Authenticator key" + }, + "autofillOptions": { + "message": "Autofill options" + }, + "websiteUri": { + "message": "Website (URI)" + }, + "websiteUriCount": { + "message": "Website (URI) $COUNT$", + "description": "Label for an input field that contains a website URI. The input field is part of a list of fields, and the count indicates the position of the field in the list.", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + } + } + }, + "websiteAdded": { + "message": "Website added" + }, + "addWebsite": { + "message": "Add website" + }, + "deleteWebsite": { + "message": "Delete website" + }, + "owner": { + "message": "Owner" + }, + "addField": { + "message": "Add field" + }, + "fieldType": { + "message": "Field type" + }, + "fieldLabel": { + "message": "Field label" + }, + "add": { + "message": "Add" + }, + "textHelpText": { + "message": "Use text fields for data like security questions" + }, + "hiddenHelpText": { + "message": "Use hidden fields for sensitive data like a password" + }, + "checkBoxHelpText": { + "message": "Use checkboxes if you'd like to autofill a form's checkbox, like a remember email" + }, + "linkedHelpText": { + "message": "Use a linked field when you are experiencing autofill issues for a specific website." + }, + "linkedLabelHelpText": { + "message": "Enter the the field's html id, name, aria-label, or placeholder." + }, "folder": { "message": "폴더" }, @@ -418,6 +476,9 @@ "message": "연결됨", "description": "This describes a field that is 'linked' (related) to another field." }, + "cfTypeCheckbox": { + "message": "Checkbox" + }, "linkedValue": { "message": "연결된 값", "description": "This describes a value that is 'linked' (related) to another value." @@ -1915,6 +1976,43 @@ } } }, + "cardDetails": { + "message": "Card details" + }, + "cardBrandDetails": { + "message": "$BRAND$ details", + "placeholders": { + "brand": { + "content": "$1", + "example": "Visa" + } + } + }, + "learnMoreAboutAuthenticators": { + "message": "Learn more about authenticators" + }, + "copyTOTP": { + "message": "Copy Authenticator key (TOTP)" + }, + "totpHelperTitle": { + "message": "Make 2-step verification seamless" + }, + "totpHelper": { + "message": "Bitwarden can store and fill 2-step verification codes. Copy and paste the key into this field." + }, + "totpHelperWithCapture": { + "message": "Bitwarden can store and fill 2-step verification codes. Select the camera icon to take a screenshot of this website's authenticator QR code, or copy and paste the key into this field." + }, + "premium": { + "message": "Premium", + "description": "Premium membership" + }, + "cardExpiredTitle": { + "message": "Expired card" + }, + "cardExpiredMessage": { + "message": "If you've renewed it, update the card's information" + }, "verificationRequired": { "message": "Verification required", "description": "Default title for the user verification dialog." @@ -2084,6 +2182,15 @@ "personalOwnershipPolicyInEffectImports": { "message": "An organization policy has blocked importing items into your individual vault." }, + "personalDetails": { + "message": "Personal details" + }, + "identification": { + "message": "Identification" + }, + "contactInfo": { + "message": "Contact information" + }, "allSends": { "message": "모든 Send", "description": "'Sends' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." @@ -2518,6 +2625,9 @@ "generateEmail": { "message": "Generate email" }, + "usernameGenerator": { + "message": "Username generator" + }, "spinboxBoundariesHint": { "message": "Value must be between $MIN$ and $MAX$.", "description": "Explains spin box minimum and maximum values to the user", @@ -3212,6 +3322,9 @@ "followTheStepsBelowToFinishLoggingIn": { "message": "Follow the steps below to finish logging in." }, + "followTheStepsBelowToFinishLoggingInWithSecurityKey": { + "message": "Follow the steps below to finish logging in with your security key." + }, "launchDuo": { "message": "Launch Duo in Browser" }, @@ -3436,6 +3549,17 @@ "ssoError": { "message": "No free ports could be found for the sso login." }, + "securePasswordGenerated": { + "message": "Secure password generated! Don't forget to also update your password on the website." + }, + "useGeneratorHelpTextPartOne": { + "message": "Use the generator", + "description": "This will be used as part of a larger sentence, broken up to include the generator icon. The full sentence will read 'Use the generator [GENERATOR_ICON] to create a strong unique password'" + }, + "useGeneratorHelpTextPartTwo": { + "message": "to create a strong unique password", + "description": "This will be used as part of a larger sentence, broken up to include the generator icon. The full sentence will read 'Use the generator [GENERATOR_ICON] to create a strong unique password'" + }, "biometricsStatusHelptextUnlockNeeded": { "message": "Biometric unlock is unavailable because PIN or password unlock is required first." }, @@ -3514,6 +3638,27 @@ "setupTwoStepLogin": { "message": "Set up two-step login" }, + "itemDetails": { + "message": "Item details" + }, + "itemName": { + "message": "Item name" + }, + "loginCredentials": { + "message": "Login credentials" + }, + "additionalOptions": { + "message": "Additional options" + }, + "itemHistory": { + "message": "Item history" + }, + "lastEdited": { + "message": "Last edited" + }, + "upload": { + "message": "Upload" + }, "newDeviceVerificationNoticeContentPage1": { "message": "Bitwarden will send a code to your account email to verify logins from new devices starting in February 2025." }, @@ -3567,5 +3712,35 @@ }, "move": { "message": "Move" + }, + "newLoginNudgeTitle": { + "message": "Save time with autofill" + }, + "newLoginNudgeBody": { + "message": "Include a Website so this login appears as an autofill suggestion." + }, + "newCardNudgeTitle": { + "message": "Seamless online checkout" + }, + "newCardNudgeBody": { + "message": "With cards, easily autofill payment forms securely and accurately." + }, + "newIdentityNudgeTitle": { + "message": "Simplify creating accounts" + }, + "newIdentityNudgeBody": { + "message": "With identities, quickly autofill long registration or contact forms." + }, + "newNoteNudgeTitle": { + "message": "Keep your sensitive data safe" + }, + "newNoteNudgeBody": { + "message": "With notes, securely store sensitive data like banking or insurance details." + }, + "newSshNudgeTitle": { + "message": "Developer-friendly SSH access" + }, + "newSshNudgeBody": { + "message": "Store your keys and connect with the SSH agent for fast, encrypted authentication." } } diff --git a/apps/desktop/src/locales/lt/messages.json b/apps/desktop/src/locales/lt/messages.json index 0a4df541985..00a5602ff3c 100644 --- a/apps/desktop/src/locales/lt/messages.json +++ b/apps/desktop/src/locales/lt/messages.json @@ -393,6 +393,64 @@ "authenticatorKeyTotp": { "message": "Autentifikavimo raktas (TOTP)" }, + "authenticatorKey": { + "message": "Authenticator key" + }, + "autofillOptions": { + "message": "Autofill options" + }, + "websiteUri": { + "message": "Website (URI)" + }, + "websiteUriCount": { + "message": "Website (URI) $COUNT$", + "description": "Label for an input field that contains a website URI. The input field is part of a list of fields, and the count indicates the position of the field in the list.", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + } + } + }, + "websiteAdded": { + "message": "Website added" + }, + "addWebsite": { + "message": "Add website" + }, + "deleteWebsite": { + "message": "Delete website" + }, + "owner": { + "message": "Owner" + }, + "addField": { + "message": "Add field" + }, + "fieldType": { + "message": "Field type" + }, + "fieldLabel": { + "message": "Field label" + }, + "add": { + "message": "Add" + }, + "textHelpText": { + "message": "Use text fields for data like security questions" + }, + "hiddenHelpText": { + "message": "Use hidden fields for sensitive data like a password" + }, + "checkBoxHelpText": { + "message": "Use checkboxes if you'd like to autofill a form's checkbox, like a remember email" + }, + "linkedHelpText": { + "message": "Use a linked field when you are experiencing autofill issues for a specific website." + }, + "linkedLabelHelpText": { + "message": "Enter the the field's html id, name, aria-label, or placeholder." + }, "folder": { "message": "Aplankas" }, @@ -418,6 +476,9 @@ "message": "Susieta", "description": "This describes a field that is 'linked' (related) to another field." }, + "cfTypeCheckbox": { + "message": "Checkbox" + }, "linkedValue": { "message": "Susieta reikšmė", "description": "This describes a value that is 'linked' (related) to another value." @@ -1915,6 +1976,43 @@ } } }, + "cardDetails": { + "message": "Card details" + }, + "cardBrandDetails": { + "message": "$BRAND$ details", + "placeholders": { + "brand": { + "content": "$1", + "example": "Visa" + } + } + }, + "learnMoreAboutAuthenticators": { + "message": "Learn more about authenticators" + }, + "copyTOTP": { + "message": "Copy Authenticator key (TOTP)" + }, + "totpHelperTitle": { + "message": "Make 2-step verification seamless" + }, + "totpHelper": { + "message": "Bitwarden can store and fill 2-step verification codes. Copy and paste the key into this field." + }, + "totpHelperWithCapture": { + "message": "Bitwarden can store and fill 2-step verification codes. Select the camera icon to take a screenshot of this website's authenticator QR code, or copy and paste the key into this field." + }, + "premium": { + "message": "Premium", + "description": "Premium membership" + }, + "cardExpiredTitle": { + "message": "Expired card" + }, + "cardExpiredMessage": { + "message": "If you've renewed it, update the card's information" + }, "verificationRequired": { "message": "Reikalingas patvirtinimas", "description": "Default title for the user verification dialog." @@ -2084,6 +2182,15 @@ "personalOwnershipPolicyInEffectImports": { "message": "Organizacijos politika blokavo elementų importavimą į jūsų individualią saugyklą." }, + "personalDetails": { + "message": "Personal details" + }, + "identification": { + "message": "Identification" + }, + "contactInfo": { + "message": "Contact information" + }, "allSends": { "message": "Visi Sendai", "description": "'Sends' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." @@ -2518,6 +2625,9 @@ "generateEmail": { "message": "Generate email" }, + "usernameGenerator": { + "message": "Username generator" + }, "spinboxBoundariesHint": { "message": "Value must be between $MIN$ and $MAX$.", "description": "Explains spin box minimum and maximum values to the user", @@ -3212,6 +3322,9 @@ "followTheStepsBelowToFinishLoggingIn": { "message": "Follow the steps below to finish logging in." }, + "followTheStepsBelowToFinishLoggingInWithSecurityKey": { + "message": "Follow the steps below to finish logging in with your security key." + }, "launchDuo": { "message": "Launch Duo in Browser" }, @@ -3436,6 +3549,17 @@ "ssoError": { "message": "No free ports could be found for the sso login." }, + "securePasswordGenerated": { + "message": "Secure password generated! Don't forget to also update your password on the website." + }, + "useGeneratorHelpTextPartOne": { + "message": "Use the generator", + "description": "This will be used as part of a larger sentence, broken up to include the generator icon. The full sentence will read 'Use the generator [GENERATOR_ICON] to create a strong unique password'" + }, + "useGeneratorHelpTextPartTwo": { + "message": "to create a strong unique password", + "description": "This will be used as part of a larger sentence, broken up to include the generator icon. The full sentence will read 'Use the generator [GENERATOR_ICON] to create a strong unique password'" + }, "biometricsStatusHelptextUnlockNeeded": { "message": "Biometric unlock is unavailable because PIN or password unlock is required first." }, @@ -3514,6 +3638,27 @@ "setupTwoStepLogin": { "message": "Set up two-step login" }, + "itemDetails": { + "message": "Item details" + }, + "itemName": { + "message": "Item name" + }, + "loginCredentials": { + "message": "Login credentials" + }, + "additionalOptions": { + "message": "Additional options" + }, + "itemHistory": { + "message": "Item history" + }, + "lastEdited": { + "message": "Last edited" + }, + "upload": { + "message": "Upload" + }, "newDeviceVerificationNoticeContentPage1": { "message": "Bitwarden will send a code to your account email to verify logins from new devices starting in February 2025." }, @@ -3567,5 +3712,35 @@ }, "move": { "message": "Move" + }, + "newLoginNudgeTitle": { + "message": "Save time with autofill" + }, + "newLoginNudgeBody": { + "message": "Include a Website so this login appears as an autofill suggestion." + }, + "newCardNudgeTitle": { + "message": "Seamless online checkout" + }, + "newCardNudgeBody": { + "message": "With cards, easily autofill payment forms securely and accurately." + }, + "newIdentityNudgeTitle": { + "message": "Simplify creating accounts" + }, + "newIdentityNudgeBody": { + "message": "With identities, quickly autofill long registration or contact forms." + }, + "newNoteNudgeTitle": { + "message": "Keep your sensitive data safe" + }, + "newNoteNudgeBody": { + "message": "With notes, securely store sensitive data like banking or insurance details." + }, + "newSshNudgeTitle": { + "message": "Developer-friendly SSH access" + }, + "newSshNudgeBody": { + "message": "Store your keys and connect with the SSH agent for fast, encrypted authentication." } } diff --git a/apps/desktop/src/locales/lv/messages.json b/apps/desktop/src/locales/lv/messages.json index 8ab3949bc64..4a3ed41f980 100644 --- a/apps/desktop/src/locales/lv/messages.json +++ b/apps/desktop/src/locales/lv/messages.json @@ -393,6 +393,64 @@ "authenticatorKeyTotp": { "message": "Autentificētāja atslēga (TOTP)" }, + "authenticatorKey": { + "message": "Autentificētāja atslēga" + }, + "autofillOptions": { + "message": "Automātiskās aizpildes iespējas" + }, + "websiteUri": { + "message": "Tīmekļvietne (URI)" + }, + "websiteUriCount": { + "message": "Tīmekļvietne (URI) $COUNT$", + "description": "Label for an input field that contains a website URI. The input field is part of a list of fields, and the count indicates the position of the field in the list.", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + } + } + }, + "websiteAdded": { + "message": "Tīmekļvietne pievienota" + }, + "addWebsite": { + "message": "Pievient tīmekļvietni" + }, + "deleteWebsite": { + "message": "Izdzēst tīmekļvietni" + }, + "owner": { + "message": "Īpašnieks" + }, + "addField": { + "message": "Pievienot lauku" + }, + "fieldType": { + "message": "Lauka veids" + }, + "fieldLabel": { + "message": "Lauka iezīme" + }, + "add": { + "message": "Pievienot" + }, + "textHelpText": { + "message": "Teksta lauki ir izmantojami tādai informācijai kā drošības jautājumi" + }, + "hiddenHelpText": { + "message": "Paslēptie lauki ir izmantojami tādai slepenai informācijai kā parole" + }, + "checkBoxHelpText": { + "message": "Izvēles rūtiņas ir izmantojamas, ja ir vajadzība automātiski aizpildīt veidlapas izvēles rūtiņu, piemēram, atcerēties e-pasta adresi" + }, + "linkedHelpText": { + "message": "Saistītais lauks ir izmantojams, kad noteiktā tīmekļvietnē tiek pieredzētas nepilnības ar automātisko aizpildi." + }, + "linkedLabelHelpText": { + "message": "Jāievada lauka HTML id, name, aria-label vai placeholder vērtība." + }, "folder": { "message": "Mape" }, @@ -418,6 +476,9 @@ "message": "Saistīts", "description": "This describes a field that is 'linked' (related) to another field." }, + "cfTypeCheckbox": { + "message": "Izvēles rūtiņa" + }, "linkedValue": { "message": "Saistīta vērtība", "description": "This describes a value that is 'linked' (related) to another value." @@ -919,7 +980,7 @@ "message": "Atlasīt divpakāpju pieteikšanās veidu" }, "selfHostedEnvironment": { - "message": "Pašuzturēta vide" + "message": "Pašmitināta vide" }, "selfHostedBaseUrlHint": { "message": "Jānorāda sava pašizvietotā Bitward servera pamata URL. Piemērs: https://bitwarden.uznemums.lv" @@ -1084,10 +1145,10 @@ "message": "Iegūt pārlūka paplašinājumu" }, "syncingComplete": { - "message": "Sinhronizācija pabeigta" + "message": "Sinhronizēšana pabeigta" }, "syncingFailed": { - "message": "Sinhronizācija neizdevās" + "message": "Sinhronizēšana neizdevās" }, "yourVaultIsLocked": { "message": "Glabātava ir aizslēgta. Jāapliecina sava identitāte, lai turpinātu." @@ -1915,6 +1976,43 @@ } } }, + "cardDetails": { + "message": "Kartes dati" + }, + "cardBrandDetails": { + "message": "$BRAND$ dati", + "placeholders": { + "brand": { + "content": "$1", + "example": "Visa" + } + } + }, + "learnMoreAboutAuthenticators": { + "message": "Uzzināt vairāk par autentificētājiem" + }, + "copyTOTP": { + "message": "Ievietot starpliktuvē autentificētāja atslēgu (TOTP)" + }, + "totpHelperTitle": { + "message": "Padarīt divpakāpju apliecināšanu plūdenu" + }, + "totpHelper": { + "message": "Bitwarden var glabāt un aizpildīt divpakāpju apliecināšanas kodus. Atslēga jāievieto starpliktuvē un jāielīmē šajā laukā." + }, + "totpHelperWithCapture": { + "message": "Bitwarden var glabāt un aizpildīt divpakāpju apliecināšanas kodus. Jāizvēlas kameras ikona, lai veiktu tīmekļvietnes autentificētāja kvadrātkoda ekrānuzņēmumu, vai jāievieto starpliktuvē atslēga un jāielīmē šajā laukā." + }, + "premium": { + "message": "Premium", + "description": "Premium membership" + }, + "cardExpiredTitle": { + "message": "Beidzies kartes derīgums" + }, + "cardExpiredMessage": { + "message": "Ja atjaunoji to, jāatjaunina kartes informācija" + }, "verificationRequired": { "message": "Nepieciešams apliecinājums", "description": "Default title for the user verification dialog." @@ -2084,6 +2182,15 @@ "personalOwnershipPolicyInEffectImports": { "message": "Apvienības nosacījums neļauj ievietot ārējos vienumus savā personīgajā glabātavā." }, + "personalDetails": { + "message": "Personiskā informācija" + }, + "identification": { + "message": "Identifikācija" + }, + "contactInfo": { + "message": "Saziņas informācija" + }, "allSends": { "message": "Visi Send", "description": "'Sends' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." @@ -2401,7 +2508,7 @@ "message": "Galvenā parole tika noņemta." }, "convertOrganizationEncryptionDesc": { - "message": "$ORGANIZATION$ izmanto vienoto pieteikšanos ar pašizvietotu atslēgu serveri. Tās dalībniekiem vairs nav nepieciešama galvenā parole, lai pieteiktos.", + "message": "$ORGANIZATION$ izmanto vienoto pieteikšanos ar pašmitinātu atslēgu serveri. Tās dalībniekiem vairs nav nepieciešama galvenā parole, lai pieteiktos.", "placeholders": { "organization": { "content": "$1", @@ -2518,6 +2625,9 @@ "generateEmail": { "message": "Izveidot e-pasta adresi" }, + "usernameGenerator": { + "message": "Lietotājvārdu veidotājs" + }, "spinboxBoundariesHint": { "message": "Vērtībai jābūt starp $MIN$ un $MAX$.", "description": "Explains spin box minimum and maximum values to the user", @@ -3013,7 +3123,7 @@ "description": "European Union" }, "selfHostedServer": { - "message": "pašizvietots" + "message": "pašmitināts" }, "accessDenied": { "message": "Piekļuve liegta. Nav nepieciešamo atļauju, lai skatītu šo lapu." @@ -3212,6 +3322,9 @@ "followTheStepsBelowToFinishLoggingIn": { "message": "Jāseko zemāk esošajām norādēm, lai pabeigtu pieteikšanos." }, + "followTheStepsBelowToFinishLoggingInWithSecurityKey": { + "message": "Jāizpilda zemāk esošās darbības, lai pabeigtu pieteikšanos ar savu drošības atslēgu." + }, "launchDuo": { "message": "Palaist Duo pārlūkā" }, @@ -3436,6 +3549,17 @@ "ssoError": { "message": "Netika atrasti brīvi vienotās (SSO) pieteikšanās porti." }, + "securePasswordGenerated": { + "message": "Izveidota droša parole. Neaizmirsti arī atjaunināt savu paroli tīmekļvietnē!" + }, + "useGeneratorHelpTextPartOne": { + "message": "Veidotājs ir izmantojams,", + "description": "This will be used as part of a larger sentence, broken up to include the generator icon. The full sentence will read 'Use the generator [GENERATOR_ICON] to create a strong unique password'" + }, + "useGeneratorHelpTextPartTwo": { + "message": "lai izveidotu spēcīgu un neatkārtojamu paroli", + "description": "This will be used as part of a larger sentence, broken up to include the generator icon. The full sentence will read 'Use the generator [GENERATOR_ICON] to create a strong unique password'" + }, "biometricsStatusHelptextUnlockNeeded": { "message": "Atslēgšana ar biometriju nav pieejama, jo vispirms ir nepieciešama atslēgšana ar PIN vai paroli." }, @@ -3514,6 +3638,27 @@ "setupTwoStepLogin": { "message": "Iestatīt divpakāpju pieteikšanos" }, + "itemDetails": { + "message": "Vienuma dati" + }, + "itemName": { + "message": "Vienuma nosaukums" + }, + "loginCredentials": { + "message": "Pieteikšanās dati" + }, + "additionalOptions": { + "message": "Papildu iespējas" + }, + "itemHistory": { + "message": "Vienuma vēsture" + }, + "lastEdited": { + "message": "Pēdējoreiz labots" + }, + "upload": { + "message": "Augšupielādēt" + }, "newDeviceVerificationNoticeContentPage1": { "message": "Bitwarden, sākot ar 2025. gada februāri, nosūtīs kodu uz konta e-pasta adresi, lai apliecinātu pieteikšanos jaunās ierīcēs." }, @@ -3567,5 +3712,35 @@ }, "move": { "message": "Pārvietot" + }, + "newLoginNudgeTitle": { + "message": "Laika ietaupīšana ar automātisko aizpildi" + }, + "newLoginNudgeBody": { + "message": "Iekļauj tīmekļvietni, lai šis pieteikšanās vienums parādītos kā automātiskās aizpildes ieteikums!" + }, + "newCardNudgeTitle": { + "message": "Plūdena apmaksa tiešsaistē" + }, + "newCardNudgeBody": { + "message": "Ar kartēm ir viegli automātiski aizpildīt maksājumu veidlapu droši un rūpīgi." + }, + "newIdentityNudgeTitle": { + "message": "Kontu izveidošanas vienkāršošana" + }, + "newIdentityNudgeBody": { + "message": "Ar identitātēm var ātri automātiski aizpildīt garas reģistrēšanās vai saziņas veidlapas." + }, + "newNoteNudgeTitle": { + "message": "Turi savus jūtīgos datus drošībā" + }, + "newNoteNudgeBody": { + "message": "Piezīmēs var droši glabāt jūtīgus datus, piemēram, bankas vai apdrošināšanas informāciju." + }, + "newSshNudgeTitle": { + "message": "Izstrādātājiem draudzīga SSH piekļuve" + }, + "newSshNudgeBody": { + "message": "Glabā savas atslēgas un savienojies ar SSH aģentu ātrai, šifrētai autentificēšanai!" } } diff --git a/apps/desktop/src/locales/me/messages.json b/apps/desktop/src/locales/me/messages.json index 5ce7ecd403c..2ee83d6e91b 100644 --- a/apps/desktop/src/locales/me/messages.json +++ b/apps/desktop/src/locales/me/messages.json @@ -393,6 +393,64 @@ "authenticatorKeyTotp": { "message": "Autentifikacioni ključ (TOTP)" }, + "authenticatorKey": { + "message": "Authenticator key" + }, + "autofillOptions": { + "message": "Autofill options" + }, + "websiteUri": { + "message": "Website (URI)" + }, + "websiteUriCount": { + "message": "Website (URI) $COUNT$", + "description": "Label for an input field that contains a website URI. The input field is part of a list of fields, and the count indicates the position of the field in the list.", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + } + } + }, + "websiteAdded": { + "message": "Website added" + }, + "addWebsite": { + "message": "Add website" + }, + "deleteWebsite": { + "message": "Delete website" + }, + "owner": { + "message": "Owner" + }, + "addField": { + "message": "Add field" + }, + "fieldType": { + "message": "Field type" + }, + "fieldLabel": { + "message": "Field label" + }, + "add": { + "message": "Add" + }, + "textHelpText": { + "message": "Use text fields for data like security questions" + }, + "hiddenHelpText": { + "message": "Use hidden fields for sensitive data like a password" + }, + "checkBoxHelpText": { + "message": "Use checkboxes if you'd like to autofill a form's checkbox, like a remember email" + }, + "linkedHelpText": { + "message": "Use a linked field when you are experiencing autofill issues for a specific website." + }, + "linkedLabelHelpText": { + "message": "Enter the the field's html id, name, aria-label, or placeholder." + }, "folder": { "message": "Fascikla" }, @@ -418,6 +476,9 @@ "message": "Povezan", "description": "This describes a field that is 'linked' (related) to another field." }, + "cfTypeCheckbox": { + "message": "Checkbox" + }, "linkedValue": { "message": "Povezana vrijednost", "description": "This describes a value that is 'linked' (related) to another value." @@ -1915,6 +1976,43 @@ } } }, + "cardDetails": { + "message": "Card details" + }, + "cardBrandDetails": { + "message": "$BRAND$ details", + "placeholders": { + "brand": { + "content": "$1", + "example": "Visa" + } + } + }, + "learnMoreAboutAuthenticators": { + "message": "Learn more about authenticators" + }, + "copyTOTP": { + "message": "Copy Authenticator key (TOTP)" + }, + "totpHelperTitle": { + "message": "Make 2-step verification seamless" + }, + "totpHelper": { + "message": "Bitwarden can store and fill 2-step verification codes. Copy and paste the key into this field." + }, + "totpHelperWithCapture": { + "message": "Bitwarden can store and fill 2-step verification codes. Select the camera icon to take a screenshot of this website's authenticator QR code, or copy and paste the key into this field." + }, + "premium": { + "message": "Premium", + "description": "Premium membership" + }, + "cardExpiredTitle": { + "message": "Expired card" + }, + "cardExpiredMessage": { + "message": "If you've renewed it, update the card's information" + }, "verificationRequired": { "message": "Verification required", "description": "Default title for the user verification dialog." @@ -2084,6 +2182,15 @@ "personalOwnershipPolicyInEffectImports": { "message": "An organization policy has blocked importing items into your individual vault." }, + "personalDetails": { + "message": "Personal details" + }, + "identification": { + "message": "Identification" + }, + "contactInfo": { + "message": "Contact information" + }, "allSends": { "message": "All Sends", "description": "'Sends' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." @@ -2518,6 +2625,9 @@ "generateEmail": { "message": "Generate email" }, + "usernameGenerator": { + "message": "Username generator" + }, "spinboxBoundariesHint": { "message": "Value must be between $MIN$ and $MAX$.", "description": "Explains spin box minimum and maximum values to the user", @@ -3212,6 +3322,9 @@ "followTheStepsBelowToFinishLoggingIn": { "message": "Follow the steps below to finish logging in." }, + "followTheStepsBelowToFinishLoggingInWithSecurityKey": { + "message": "Follow the steps below to finish logging in with your security key." + }, "launchDuo": { "message": "Launch Duo in Browser" }, @@ -3436,6 +3549,17 @@ "ssoError": { "message": "No free ports could be found for the sso login." }, + "securePasswordGenerated": { + "message": "Secure password generated! Don't forget to also update your password on the website." + }, + "useGeneratorHelpTextPartOne": { + "message": "Use the generator", + "description": "This will be used as part of a larger sentence, broken up to include the generator icon. The full sentence will read 'Use the generator [GENERATOR_ICON] to create a strong unique password'" + }, + "useGeneratorHelpTextPartTwo": { + "message": "to create a strong unique password", + "description": "This will be used as part of a larger sentence, broken up to include the generator icon. The full sentence will read 'Use the generator [GENERATOR_ICON] to create a strong unique password'" + }, "biometricsStatusHelptextUnlockNeeded": { "message": "Biometric unlock is unavailable because PIN or password unlock is required first." }, @@ -3514,6 +3638,27 @@ "setupTwoStepLogin": { "message": "Set up two-step login" }, + "itemDetails": { + "message": "Item details" + }, + "itemName": { + "message": "Item name" + }, + "loginCredentials": { + "message": "Login credentials" + }, + "additionalOptions": { + "message": "Additional options" + }, + "itemHistory": { + "message": "Item history" + }, + "lastEdited": { + "message": "Last edited" + }, + "upload": { + "message": "Upload" + }, "newDeviceVerificationNoticeContentPage1": { "message": "Bitwarden will send a code to your account email to verify logins from new devices starting in February 2025." }, @@ -3567,5 +3712,35 @@ }, "move": { "message": "Move" + }, + "newLoginNudgeTitle": { + "message": "Save time with autofill" + }, + "newLoginNudgeBody": { + "message": "Include a Website so this login appears as an autofill suggestion." + }, + "newCardNudgeTitle": { + "message": "Seamless online checkout" + }, + "newCardNudgeBody": { + "message": "With cards, easily autofill payment forms securely and accurately." + }, + "newIdentityNudgeTitle": { + "message": "Simplify creating accounts" + }, + "newIdentityNudgeBody": { + "message": "With identities, quickly autofill long registration or contact forms." + }, + "newNoteNudgeTitle": { + "message": "Keep your sensitive data safe" + }, + "newNoteNudgeBody": { + "message": "With notes, securely store sensitive data like banking or insurance details." + }, + "newSshNudgeTitle": { + "message": "Developer-friendly SSH access" + }, + "newSshNudgeBody": { + "message": "Store your keys and connect with the SSH agent for fast, encrypted authentication." } } diff --git a/apps/desktop/src/locales/ml/messages.json b/apps/desktop/src/locales/ml/messages.json index 568095899f3..9ce30119832 100644 --- a/apps/desktop/src/locales/ml/messages.json +++ b/apps/desktop/src/locales/ml/messages.json @@ -393,6 +393,64 @@ "authenticatorKeyTotp": { "message": "ഓതന്റിക്കേറ്റർ കീ (TOTP)" }, + "authenticatorKey": { + "message": "Authenticator key" + }, + "autofillOptions": { + "message": "Autofill options" + }, + "websiteUri": { + "message": "Website (URI)" + }, + "websiteUriCount": { + "message": "Website (URI) $COUNT$", + "description": "Label for an input field that contains a website URI. The input field is part of a list of fields, and the count indicates the position of the field in the list.", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + } + } + }, + "websiteAdded": { + "message": "Website added" + }, + "addWebsite": { + "message": "Add website" + }, + "deleteWebsite": { + "message": "Delete website" + }, + "owner": { + "message": "Owner" + }, + "addField": { + "message": "Add field" + }, + "fieldType": { + "message": "Field type" + }, + "fieldLabel": { + "message": "Field label" + }, + "add": { + "message": "Add" + }, + "textHelpText": { + "message": "Use text fields for data like security questions" + }, + "hiddenHelpText": { + "message": "Use hidden fields for sensitive data like a password" + }, + "checkBoxHelpText": { + "message": "Use checkboxes if you'd like to autofill a form's checkbox, like a remember email" + }, + "linkedHelpText": { + "message": "Use a linked field when you are experiencing autofill issues for a specific website." + }, + "linkedLabelHelpText": { + "message": "Enter the the field's html id, name, aria-label, or placeholder." + }, "folder": { "message": "ഫോൾഡർ" }, @@ -418,6 +476,9 @@ "message": "Linked", "description": "This describes a field that is 'linked' (related) to another field." }, + "cfTypeCheckbox": { + "message": "Checkbox" + }, "linkedValue": { "message": "Linked value", "description": "This describes a value that is 'linked' (related) to another value." @@ -1915,6 +1976,43 @@ } } }, + "cardDetails": { + "message": "Card details" + }, + "cardBrandDetails": { + "message": "$BRAND$ details", + "placeholders": { + "brand": { + "content": "$1", + "example": "Visa" + } + } + }, + "learnMoreAboutAuthenticators": { + "message": "Learn more about authenticators" + }, + "copyTOTP": { + "message": "Copy Authenticator key (TOTP)" + }, + "totpHelperTitle": { + "message": "Make 2-step verification seamless" + }, + "totpHelper": { + "message": "Bitwarden can store and fill 2-step verification codes. Copy and paste the key into this field." + }, + "totpHelperWithCapture": { + "message": "Bitwarden can store and fill 2-step verification codes. Select the camera icon to take a screenshot of this website's authenticator QR code, or copy and paste the key into this field." + }, + "premium": { + "message": "Premium", + "description": "Premium membership" + }, + "cardExpiredTitle": { + "message": "Expired card" + }, + "cardExpiredMessage": { + "message": "If you've renewed it, update the card's information" + }, "verificationRequired": { "message": "Verification required", "description": "Default title for the user verification dialog." @@ -2084,6 +2182,15 @@ "personalOwnershipPolicyInEffectImports": { "message": "An organization policy has blocked importing items into your individual vault." }, + "personalDetails": { + "message": "Personal details" + }, + "identification": { + "message": "Identification" + }, + "contactInfo": { + "message": "Contact information" + }, "allSends": { "message": "എല്ലാം Send-കൾ", "description": "'Sends' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." @@ -2518,6 +2625,9 @@ "generateEmail": { "message": "Generate email" }, + "usernameGenerator": { + "message": "Username generator" + }, "spinboxBoundariesHint": { "message": "Value must be between $MIN$ and $MAX$.", "description": "Explains spin box minimum and maximum values to the user", @@ -3212,6 +3322,9 @@ "followTheStepsBelowToFinishLoggingIn": { "message": "Follow the steps below to finish logging in." }, + "followTheStepsBelowToFinishLoggingInWithSecurityKey": { + "message": "Follow the steps below to finish logging in with your security key." + }, "launchDuo": { "message": "Launch Duo in Browser" }, @@ -3436,6 +3549,17 @@ "ssoError": { "message": "No free ports could be found for the sso login." }, + "securePasswordGenerated": { + "message": "Secure password generated! Don't forget to also update your password on the website." + }, + "useGeneratorHelpTextPartOne": { + "message": "Use the generator", + "description": "This will be used as part of a larger sentence, broken up to include the generator icon. The full sentence will read 'Use the generator [GENERATOR_ICON] to create a strong unique password'" + }, + "useGeneratorHelpTextPartTwo": { + "message": "to create a strong unique password", + "description": "This will be used as part of a larger sentence, broken up to include the generator icon. The full sentence will read 'Use the generator [GENERATOR_ICON] to create a strong unique password'" + }, "biometricsStatusHelptextUnlockNeeded": { "message": "Biometric unlock is unavailable because PIN or password unlock is required first." }, @@ -3514,6 +3638,27 @@ "setupTwoStepLogin": { "message": "Set up two-step login" }, + "itemDetails": { + "message": "Item details" + }, + "itemName": { + "message": "Item name" + }, + "loginCredentials": { + "message": "Login credentials" + }, + "additionalOptions": { + "message": "Additional options" + }, + "itemHistory": { + "message": "Item history" + }, + "lastEdited": { + "message": "Last edited" + }, + "upload": { + "message": "Upload" + }, "newDeviceVerificationNoticeContentPage1": { "message": "Bitwarden will send a code to your account email to verify logins from new devices starting in February 2025." }, @@ -3567,5 +3712,35 @@ }, "move": { "message": "Move" + }, + "newLoginNudgeTitle": { + "message": "Save time with autofill" + }, + "newLoginNudgeBody": { + "message": "Include a Website so this login appears as an autofill suggestion." + }, + "newCardNudgeTitle": { + "message": "Seamless online checkout" + }, + "newCardNudgeBody": { + "message": "With cards, easily autofill payment forms securely and accurately." + }, + "newIdentityNudgeTitle": { + "message": "Simplify creating accounts" + }, + "newIdentityNudgeBody": { + "message": "With identities, quickly autofill long registration or contact forms." + }, + "newNoteNudgeTitle": { + "message": "Keep your sensitive data safe" + }, + "newNoteNudgeBody": { + "message": "With notes, securely store sensitive data like banking or insurance details." + }, + "newSshNudgeTitle": { + "message": "Developer-friendly SSH access" + }, + "newSshNudgeBody": { + "message": "Store your keys and connect with the SSH agent for fast, encrypted authentication." } } diff --git a/apps/desktop/src/locales/mr/messages.json b/apps/desktop/src/locales/mr/messages.json index 8850cbe5a3f..2350e0df4c7 100644 --- a/apps/desktop/src/locales/mr/messages.json +++ b/apps/desktop/src/locales/mr/messages.json @@ -393,6 +393,64 @@ "authenticatorKeyTotp": { "message": "Authenticator key (TOTP)" }, + "authenticatorKey": { + "message": "Authenticator key" + }, + "autofillOptions": { + "message": "Autofill options" + }, + "websiteUri": { + "message": "Website (URI)" + }, + "websiteUriCount": { + "message": "Website (URI) $COUNT$", + "description": "Label for an input field that contains a website URI. The input field is part of a list of fields, and the count indicates the position of the field in the list.", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + } + } + }, + "websiteAdded": { + "message": "Website added" + }, + "addWebsite": { + "message": "Add website" + }, + "deleteWebsite": { + "message": "Delete website" + }, + "owner": { + "message": "Owner" + }, + "addField": { + "message": "Add field" + }, + "fieldType": { + "message": "Field type" + }, + "fieldLabel": { + "message": "Field label" + }, + "add": { + "message": "Add" + }, + "textHelpText": { + "message": "Use text fields for data like security questions" + }, + "hiddenHelpText": { + "message": "Use hidden fields for sensitive data like a password" + }, + "checkBoxHelpText": { + "message": "Use checkboxes if you'd like to autofill a form's checkbox, like a remember email" + }, + "linkedHelpText": { + "message": "Use a linked field when you are experiencing autofill issues for a specific website." + }, + "linkedLabelHelpText": { + "message": "Enter the the field's html id, name, aria-label, or placeholder." + }, "folder": { "message": "Folder" }, @@ -418,6 +476,9 @@ "message": "Linked", "description": "This describes a field that is 'linked' (related) to another field." }, + "cfTypeCheckbox": { + "message": "Checkbox" + }, "linkedValue": { "message": "Linked value", "description": "This describes a value that is 'linked' (related) to another value." @@ -1915,6 +1976,43 @@ } } }, + "cardDetails": { + "message": "Card details" + }, + "cardBrandDetails": { + "message": "$BRAND$ details", + "placeholders": { + "brand": { + "content": "$1", + "example": "Visa" + } + } + }, + "learnMoreAboutAuthenticators": { + "message": "Learn more about authenticators" + }, + "copyTOTP": { + "message": "Copy Authenticator key (TOTP)" + }, + "totpHelperTitle": { + "message": "Make 2-step verification seamless" + }, + "totpHelper": { + "message": "Bitwarden can store and fill 2-step verification codes. Copy and paste the key into this field." + }, + "totpHelperWithCapture": { + "message": "Bitwarden can store and fill 2-step verification codes. Select the camera icon to take a screenshot of this website's authenticator QR code, or copy and paste the key into this field." + }, + "premium": { + "message": "Premium", + "description": "Premium membership" + }, + "cardExpiredTitle": { + "message": "Expired card" + }, + "cardExpiredMessage": { + "message": "If you've renewed it, update the card's information" + }, "verificationRequired": { "message": "Verification required", "description": "Default title for the user verification dialog." @@ -2084,6 +2182,15 @@ "personalOwnershipPolicyInEffectImports": { "message": "An organization policy has blocked importing items into your individual vault." }, + "personalDetails": { + "message": "Personal details" + }, + "identification": { + "message": "Identification" + }, + "contactInfo": { + "message": "Contact information" + }, "allSends": { "message": "All Sends", "description": "'Sends' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." @@ -2518,6 +2625,9 @@ "generateEmail": { "message": "Generate email" }, + "usernameGenerator": { + "message": "Username generator" + }, "spinboxBoundariesHint": { "message": "Value must be between $MIN$ and $MAX$.", "description": "Explains spin box minimum and maximum values to the user", @@ -3212,6 +3322,9 @@ "followTheStepsBelowToFinishLoggingIn": { "message": "Follow the steps below to finish logging in." }, + "followTheStepsBelowToFinishLoggingInWithSecurityKey": { + "message": "Follow the steps below to finish logging in with your security key." + }, "launchDuo": { "message": "Launch Duo in Browser" }, @@ -3436,6 +3549,17 @@ "ssoError": { "message": "No free ports could be found for the sso login." }, + "securePasswordGenerated": { + "message": "Secure password generated! Don't forget to also update your password on the website." + }, + "useGeneratorHelpTextPartOne": { + "message": "Use the generator", + "description": "This will be used as part of a larger sentence, broken up to include the generator icon. The full sentence will read 'Use the generator [GENERATOR_ICON] to create a strong unique password'" + }, + "useGeneratorHelpTextPartTwo": { + "message": "to create a strong unique password", + "description": "This will be used as part of a larger sentence, broken up to include the generator icon. The full sentence will read 'Use the generator [GENERATOR_ICON] to create a strong unique password'" + }, "biometricsStatusHelptextUnlockNeeded": { "message": "Biometric unlock is unavailable because PIN or password unlock is required first." }, @@ -3514,6 +3638,27 @@ "setupTwoStepLogin": { "message": "Set up two-step login" }, + "itemDetails": { + "message": "Item details" + }, + "itemName": { + "message": "Item name" + }, + "loginCredentials": { + "message": "Login credentials" + }, + "additionalOptions": { + "message": "Additional options" + }, + "itemHistory": { + "message": "Item history" + }, + "lastEdited": { + "message": "Last edited" + }, + "upload": { + "message": "Upload" + }, "newDeviceVerificationNoticeContentPage1": { "message": "Bitwarden will send a code to your account email to verify logins from new devices starting in February 2025." }, @@ -3567,5 +3712,35 @@ }, "move": { "message": "Move" + }, + "newLoginNudgeTitle": { + "message": "Save time with autofill" + }, + "newLoginNudgeBody": { + "message": "Include a Website so this login appears as an autofill suggestion." + }, + "newCardNudgeTitle": { + "message": "Seamless online checkout" + }, + "newCardNudgeBody": { + "message": "With cards, easily autofill payment forms securely and accurately." + }, + "newIdentityNudgeTitle": { + "message": "Simplify creating accounts" + }, + "newIdentityNudgeBody": { + "message": "With identities, quickly autofill long registration or contact forms." + }, + "newNoteNudgeTitle": { + "message": "Keep your sensitive data safe" + }, + "newNoteNudgeBody": { + "message": "With notes, securely store sensitive data like banking or insurance details." + }, + "newSshNudgeTitle": { + "message": "Developer-friendly SSH access" + }, + "newSshNudgeBody": { + "message": "Store your keys and connect with the SSH agent for fast, encrypted authentication." } } diff --git a/apps/desktop/src/locales/my/messages.json b/apps/desktop/src/locales/my/messages.json index 281d947d98f..b53b3fb241a 100644 --- a/apps/desktop/src/locales/my/messages.json +++ b/apps/desktop/src/locales/my/messages.json @@ -393,6 +393,64 @@ "authenticatorKeyTotp": { "message": "Authenticator key (TOTP)" }, + "authenticatorKey": { + "message": "Authenticator key" + }, + "autofillOptions": { + "message": "Autofill options" + }, + "websiteUri": { + "message": "Website (URI)" + }, + "websiteUriCount": { + "message": "Website (URI) $COUNT$", + "description": "Label for an input field that contains a website URI. The input field is part of a list of fields, and the count indicates the position of the field in the list.", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + } + } + }, + "websiteAdded": { + "message": "Website added" + }, + "addWebsite": { + "message": "Add website" + }, + "deleteWebsite": { + "message": "Delete website" + }, + "owner": { + "message": "Owner" + }, + "addField": { + "message": "Add field" + }, + "fieldType": { + "message": "Field type" + }, + "fieldLabel": { + "message": "Field label" + }, + "add": { + "message": "Add" + }, + "textHelpText": { + "message": "Use text fields for data like security questions" + }, + "hiddenHelpText": { + "message": "Use hidden fields for sensitive data like a password" + }, + "checkBoxHelpText": { + "message": "Use checkboxes if you'd like to autofill a form's checkbox, like a remember email" + }, + "linkedHelpText": { + "message": "Use a linked field when you are experiencing autofill issues for a specific website." + }, + "linkedLabelHelpText": { + "message": "Enter the the field's html id, name, aria-label, or placeholder." + }, "folder": { "message": "Folder" }, @@ -418,6 +476,9 @@ "message": "Linked", "description": "This describes a field that is 'linked' (related) to another field." }, + "cfTypeCheckbox": { + "message": "Checkbox" + }, "linkedValue": { "message": "Linked value", "description": "This describes a value that is 'linked' (related) to another value." @@ -1915,6 +1976,43 @@ } } }, + "cardDetails": { + "message": "Card details" + }, + "cardBrandDetails": { + "message": "$BRAND$ details", + "placeholders": { + "brand": { + "content": "$1", + "example": "Visa" + } + } + }, + "learnMoreAboutAuthenticators": { + "message": "Learn more about authenticators" + }, + "copyTOTP": { + "message": "Copy Authenticator key (TOTP)" + }, + "totpHelperTitle": { + "message": "Make 2-step verification seamless" + }, + "totpHelper": { + "message": "Bitwarden can store and fill 2-step verification codes. Copy and paste the key into this field." + }, + "totpHelperWithCapture": { + "message": "Bitwarden can store and fill 2-step verification codes. Select the camera icon to take a screenshot of this website's authenticator QR code, or copy and paste the key into this field." + }, + "premium": { + "message": "Premium", + "description": "Premium membership" + }, + "cardExpiredTitle": { + "message": "Expired card" + }, + "cardExpiredMessage": { + "message": "If you've renewed it, update the card's information" + }, "verificationRequired": { "message": "Verification required", "description": "Default title for the user verification dialog." @@ -2084,6 +2182,15 @@ "personalOwnershipPolicyInEffectImports": { "message": "An organization policy has blocked importing items into your individual vault." }, + "personalDetails": { + "message": "Personal details" + }, + "identification": { + "message": "Identification" + }, + "contactInfo": { + "message": "Contact information" + }, "allSends": { "message": "All Sends", "description": "'Sends' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." @@ -2518,6 +2625,9 @@ "generateEmail": { "message": "Generate email" }, + "usernameGenerator": { + "message": "Username generator" + }, "spinboxBoundariesHint": { "message": "Value must be between $MIN$ and $MAX$.", "description": "Explains spin box minimum and maximum values to the user", @@ -3212,6 +3322,9 @@ "followTheStepsBelowToFinishLoggingIn": { "message": "Follow the steps below to finish logging in." }, + "followTheStepsBelowToFinishLoggingInWithSecurityKey": { + "message": "Follow the steps below to finish logging in with your security key." + }, "launchDuo": { "message": "Launch Duo in Browser" }, @@ -3436,6 +3549,17 @@ "ssoError": { "message": "No free ports could be found for the sso login." }, + "securePasswordGenerated": { + "message": "Secure password generated! Don't forget to also update your password on the website." + }, + "useGeneratorHelpTextPartOne": { + "message": "Use the generator", + "description": "This will be used as part of a larger sentence, broken up to include the generator icon. The full sentence will read 'Use the generator [GENERATOR_ICON] to create a strong unique password'" + }, + "useGeneratorHelpTextPartTwo": { + "message": "to create a strong unique password", + "description": "This will be used as part of a larger sentence, broken up to include the generator icon. The full sentence will read 'Use the generator [GENERATOR_ICON] to create a strong unique password'" + }, "biometricsStatusHelptextUnlockNeeded": { "message": "Biometric unlock is unavailable because PIN or password unlock is required first." }, @@ -3514,6 +3638,27 @@ "setupTwoStepLogin": { "message": "Set up two-step login" }, + "itemDetails": { + "message": "Item details" + }, + "itemName": { + "message": "Item name" + }, + "loginCredentials": { + "message": "Login credentials" + }, + "additionalOptions": { + "message": "Additional options" + }, + "itemHistory": { + "message": "Item history" + }, + "lastEdited": { + "message": "Last edited" + }, + "upload": { + "message": "Upload" + }, "newDeviceVerificationNoticeContentPage1": { "message": "Bitwarden will send a code to your account email to verify logins from new devices starting in February 2025." }, @@ -3567,5 +3712,35 @@ }, "move": { "message": "Move" + }, + "newLoginNudgeTitle": { + "message": "Save time with autofill" + }, + "newLoginNudgeBody": { + "message": "Include a Website so this login appears as an autofill suggestion." + }, + "newCardNudgeTitle": { + "message": "Seamless online checkout" + }, + "newCardNudgeBody": { + "message": "With cards, easily autofill payment forms securely and accurately." + }, + "newIdentityNudgeTitle": { + "message": "Simplify creating accounts" + }, + "newIdentityNudgeBody": { + "message": "With identities, quickly autofill long registration or contact forms." + }, + "newNoteNudgeTitle": { + "message": "Keep your sensitive data safe" + }, + "newNoteNudgeBody": { + "message": "With notes, securely store sensitive data like banking or insurance details." + }, + "newSshNudgeTitle": { + "message": "Developer-friendly SSH access" + }, + "newSshNudgeBody": { + "message": "Store your keys and connect with the SSH agent for fast, encrypted authentication." } } diff --git a/apps/desktop/src/locales/nb/messages.json b/apps/desktop/src/locales/nb/messages.json index 0318b040250..f0e6b338bc8 100644 --- a/apps/desktop/src/locales/nb/messages.json +++ b/apps/desktop/src/locales/nb/messages.json @@ -393,6 +393,64 @@ "authenticatorKeyTotp": { "message": "Autentiseringsnøkkel (TOTP)" }, + "authenticatorKey": { + "message": "Authenticator key" + }, + "autofillOptions": { + "message": "Autofill options" + }, + "websiteUri": { + "message": "Website (URI)" + }, + "websiteUriCount": { + "message": "Website (URI) $COUNT$", + "description": "Label for an input field that contains a website URI. The input field is part of a list of fields, and the count indicates the position of the field in the list.", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + } + } + }, + "websiteAdded": { + "message": "Website added" + }, + "addWebsite": { + "message": "Add website" + }, + "deleteWebsite": { + "message": "Delete website" + }, + "owner": { + "message": "Owner" + }, + "addField": { + "message": "Add field" + }, + "fieldType": { + "message": "Field type" + }, + "fieldLabel": { + "message": "Field label" + }, + "add": { + "message": "Add" + }, + "textHelpText": { + "message": "Use text fields for data like security questions" + }, + "hiddenHelpText": { + "message": "Use hidden fields for sensitive data like a password" + }, + "checkBoxHelpText": { + "message": "Use checkboxes if you'd like to autofill a form's checkbox, like a remember email" + }, + "linkedHelpText": { + "message": "Use a linked field when you are experiencing autofill issues for a specific website." + }, + "linkedLabelHelpText": { + "message": "Enter the the field's html id, name, aria-label, or placeholder." + }, "folder": { "message": "Mappe" }, @@ -418,6 +476,9 @@ "message": "Tilknyttet", "description": "This describes a field that is 'linked' (related) to another field." }, + "cfTypeCheckbox": { + "message": "Checkbox" + }, "linkedValue": { "message": "Tilknyttet verdi", "description": "This describes a value that is 'linked' (related) to another value." @@ -1915,6 +1976,43 @@ } } }, + "cardDetails": { + "message": "Card details" + }, + "cardBrandDetails": { + "message": "$BRAND$ details", + "placeholders": { + "brand": { + "content": "$1", + "example": "Visa" + } + } + }, + "learnMoreAboutAuthenticators": { + "message": "Learn more about authenticators" + }, + "copyTOTP": { + "message": "Copy Authenticator key (TOTP)" + }, + "totpHelperTitle": { + "message": "Make 2-step verification seamless" + }, + "totpHelper": { + "message": "Bitwarden can store and fill 2-step verification codes. Copy and paste the key into this field." + }, + "totpHelperWithCapture": { + "message": "Bitwarden can store and fill 2-step verification codes. Select the camera icon to take a screenshot of this website's authenticator QR code, or copy and paste the key into this field." + }, + "premium": { + "message": "Premium", + "description": "Premium membership" + }, + "cardExpiredTitle": { + "message": "Expired card" + }, + "cardExpiredMessage": { + "message": "If you've renewed it, update the card's information" + }, "verificationRequired": { "message": "Verifisering kreves", "description": "Default title for the user verification dialog." @@ -2084,6 +2182,15 @@ "personalOwnershipPolicyInEffectImports": { "message": "An organization policy has blocked importing items into your individual vault." }, + "personalDetails": { + "message": "Personal details" + }, + "identification": { + "message": "Identification" + }, + "contactInfo": { + "message": "Contact information" + }, "allSends": { "message": "Alle Send-er", "description": "'Sends' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." @@ -2518,6 +2625,9 @@ "generateEmail": { "message": "Generér E-post" }, + "usernameGenerator": { + "message": "Username generator" + }, "spinboxBoundariesHint": { "message": "Verdien må være mellom $MIN$ og $MAX$.", "description": "Explains spin box minimum and maximum values to the user", @@ -3212,6 +3322,9 @@ "followTheStepsBelowToFinishLoggingIn": { "message": "Follow the steps below to finish logging in." }, + "followTheStepsBelowToFinishLoggingInWithSecurityKey": { + "message": "Follow the steps below to finish logging in with your security key." + }, "launchDuo": { "message": "Launch Duo in Browser" }, @@ -3436,6 +3549,17 @@ "ssoError": { "message": "No free ports could be found for the sso login." }, + "securePasswordGenerated": { + "message": "Secure password generated! Don't forget to also update your password on the website." + }, + "useGeneratorHelpTextPartOne": { + "message": "Use the generator", + "description": "This will be used as part of a larger sentence, broken up to include the generator icon. The full sentence will read 'Use the generator [GENERATOR_ICON] to create a strong unique password'" + }, + "useGeneratorHelpTextPartTwo": { + "message": "to create a strong unique password", + "description": "This will be used as part of a larger sentence, broken up to include the generator icon. The full sentence will read 'Use the generator [GENERATOR_ICON] to create a strong unique password'" + }, "biometricsStatusHelptextUnlockNeeded": { "message": "Biometric unlock is unavailable because PIN or password unlock is required first." }, @@ -3514,6 +3638,27 @@ "setupTwoStepLogin": { "message": "Sett opp 2-trinnspålogging" }, + "itemDetails": { + "message": "Item details" + }, + "itemName": { + "message": "Item name" + }, + "loginCredentials": { + "message": "Login credentials" + }, + "additionalOptions": { + "message": "Additional options" + }, + "itemHistory": { + "message": "Item history" + }, + "lastEdited": { + "message": "Last edited" + }, + "upload": { + "message": "Upload" + }, "newDeviceVerificationNoticeContentPage1": { "message": "Bitwarden will send a code to your account email to verify logins from new devices starting in February 2025." }, @@ -3567,5 +3712,35 @@ }, "move": { "message": "Move" + }, + "newLoginNudgeTitle": { + "message": "Save time with autofill" + }, + "newLoginNudgeBody": { + "message": "Include a Website so this login appears as an autofill suggestion." + }, + "newCardNudgeTitle": { + "message": "Seamless online checkout" + }, + "newCardNudgeBody": { + "message": "With cards, easily autofill payment forms securely and accurately." + }, + "newIdentityNudgeTitle": { + "message": "Simplify creating accounts" + }, + "newIdentityNudgeBody": { + "message": "With identities, quickly autofill long registration or contact forms." + }, + "newNoteNudgeTitle": { + "message": "Keep your sensitive data safe" + }, + "newNoteNudgeBody": { + "message": "With notes, securely store sensitive data like banking or insurance details." + }, + "newSshNudgeTitle": { + "message": "Developer-friendly SSH access" + }, + "newSshNudgeBody": { + "message": "Store your keys and connect with the SSH agent for fast, encrypted authentication." } } diff --git a/apps/desktop/src/locales/ne/messages.json b/apps/desktop/src/locales/ne/messages.json index 3fffd5caf74..93485f6d773 100644 --- a/apps/desktop/src/locales/ne/messages.json +++ b/apps/desktop/src/locales/ne/messages.json @@ -393,6 +393,64 @@ "authenticatorKeyTotp": { "message": "Authenticator key (TOTP)" }, + "authenticatorKey": { + "message": "Authenticator key" + }, + "autofillOptions": { + "message": "Autofill options" + }, + "websiteUri": { + "message": "Website (URI)" + }, + "websiteUriCount": { + "message": "Website (URI) $COUNT$", + "description": "Label for an input field that contains a website URI. The input field is part of a list of fields, and the count indicates the position of the field in the list.", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + } + } + }, + "websiteAdded": { + "message": "Website added" + }, + "addWebsite": { + "message": "Add website" + }, + "deleteWebsite": { + "message": "Delete website" + }, + "owner": { + "message": "Owner" + }, + "addField": { + "message": "Add field" + }, + "fieldType": { + "message": "Field type" + }, + "fieldLabel": { + "message": "Field label" + }, + "add": { + "message": "Add" + }, + "textHelpText": { + "message": "Use text fields for data like security questions" + }, + "hiddenHelpText": { + "message": "Use hidden fields for sensitive data like a password" + }, + "checkBoxHelpText": { + "message": "Use checkboxes if you'd like to autofill a form's checkbox, like a remember email" + }, + "linkedHelpText": { + "message": "Use a linked field when you are experiencing autofill issues for a specific website." + }, + "linkedLabelHelpText": { + "message": "Enter the the field's html id, name, aria-label, or placeholder." + }, "folder": { "message": "Folder" }, @@ -418,6 +476,9 @@ "message": "Linked", "description": "This describes a field that is 'linked' (related) to another field." }, + "cfTypeCheckbox": { + "message": "Checkbox" + }, "linkedValue": { "message": "Linked value", "description": "This describes a value that is 'linked' (related) to another value." @@ -1915,6 +1976,43 @@ } } }, + "cardDetails": { + "message": "Card details" + }, + "cardBrandDetails": { + "message": "$BRAND$ details", + "placeholders": { + "brand": { + "content": "$1", + "example": "Visa" + } + } + }, + "learnMoreAboutAuthenticators": { + "message": "Learn more about authenticators" + }, + "copyTOTP": { + "message": "Copy Authenticator key (TOTP)" + }, + "totpHelperTitle": { + "message": "Make 2-step verification seamless" + }, + "totpHelper": { + "message": "Bitwarden can store and fill 2-step verification codes. Copy and paste the key into this field." + }, + "totpHelperWithCapture": { + "message": "Bitwarden can store and fill 2-step verification codes. Select the camera icon to take a screenshot of this website's authenticator QR code, or copy and paste the key into this field." + }, + "premium": { + "message": "Premium", + "description": "Premium membership" + }, + "cardExpiredTitle": { + "message": "Expired card" + }, + "cardExpiredMessage": { + "message": "If you've renewed it, update the card's information" + }, "verificationRequired": { "message": "Verification required", "description": "Default title for the user verification dialog." @@ -2084,6 +2182,15 @@ "personalOwnershipPolicyInEffectImports": { "message": "An organization policy has blocked importing items into your individual vault." }, + "personalDetails": { + "message": "Personal details" + }, + "identification": { + "message": "Identification" + }, + "contactInfo": { + "message": "Contact information" + }, "allSends": { "message": "All Sends", "description": "'Sends' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." @@ -2518,6 +2625,9 @@ "generateEmail": { "message": "Generate email" }, + "usernameGenerator": { + "message": "Username generator" + }, "spinboxBoundariesHint": { "message": "Value must be between $MIN$ and $MAX$.", "description": "Explains spin box minimum and maximum values to the user", @@ -3212,6 +3322,9 @@ "followTheStepsBelowToFinishLoggingIn": { "message": "Follow the steps below to finish logging in." }, + "followTheStepsBelowToFinishLoggingInWithSecurityKey": { + "message": "Follow the steps below to finish logging in with your security key." + }, "launchDuo": { "message": "Launch Duo in Browser" }, @@ -3436,6 +3549,17 @@ "ssoError": { "message": "No free ports could be found for the sso login." }, + "securePasswordGenerated": { + "message": "Secure password generated! Don't forget to also update your password on the website." + }, + "useGeneratorHelpTextPartOne": { + "message": "Use the generator", + "description": "This will be used as part of a larger sentence, broken up to include the generator icon. The full sentence will read 'Use the generator [GENERATOR_ICON] to create a strong unique password'" + }, + "useGeneratorHelpTextPartTwo": { + "message": "to create a strong unique password", + "description": "This will be used as part of a larger sentence, broken up to include the generator icon. The full sentence will read 'Use the generator [GENERATOR_ICON] to create a strong unique password'" + }, "biometricsStatusHelptextUnlockNeeded": { "message": "Biometric unlock is unavailable because PIN or password unlock is required first." }, @@ -3514,6 +3638,27 @@ "setupTwoStepLogin": { "message": "Set up two-step login" }, + "itemDetails": { + "message": "Item details" + }, + "itemName": { + "message": "Item name" + }, + "loginCredentials": { + "message": "Login credentials" + }, + "additionalOptions": { + "message": "Additional options" + }, + "itemHistory": { + "message": "Item history" + }, + "lastEdited": { + "message": "Last edited" + }, + "upload": { + "message": "Upload" + }, "newDeviceVerificationNoticeContentPage1": { "message": "Bitwarden will send a code to your account email to verify logins from new devices starting in February 2025." }, @@ -3567,5 +3712,35 @@ }, "move": { "message": "Move" + }, + "newLoginNudgeTitle": { + "message": "Save time with autofill" + }, + "newLoginNudgeBody": { + "message": "Include a Website so this login appears as an autofill suggestion." + }, + "newCardNudgeTitle": { + "message": "Seamless online checkout" + }, + "newCardNudgeBody": { + "message": "With cards, easily autofill payment forms securely and accurately." + }, + "newIdentityNudgeTitle": { + "message": "Simplify creating accounts" + }, + "newIdentityNudgeBody": { + "message": "With identities, quickly autofill long registration or contact forms." + }, + "newNoteNudgeTitle": { + "message": "Keep your sensitive data safe" + }, + "newNoteNudgeBody": { + "message": "With notes, securely store sensitive data like banking or insurance details." + }, + "newSshNudgeTitle": { + "message": "Developer-friendly SSH access" + }, + "newSshNudgeBody": { + "message": "Store your keys and connect with the SSH agent for fast, encrypted authentication." } } diff --git a/apps/desktop/src/locales/nl/messages.json b/apps/desktop/src/locales/nl/messages.json index 6e7d8eab7ed..dee51830601 100644 --- a/apps/desktop/src/locales/nl/messages.json +++ b/apps/desktop/src/locales/nl/messages.json @@ -393,6 +393,64 @@ "authenticatorKeyTotp": { "message": "Authenticatiesleutel (TOTP)" }, + "authenticatorKey": { + "message": "Authenticatiesleutel" + }, + "autofillOptions": { + "message": "Instellingen automatisch invullen" + }, + "websiteUri": { + "message": "Website (URI)" + }, + "websiteUriCount": { + "message": "Website (URI) $COUNT$", + "description": "Label for an input field that contains a website URI. The input field is part of a list of fields, and the count indicates the position of the field in the list.", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + } + } + }, + "websiteAdded": { + "message": "Website toegevoegd" + }, + "addWebsite": { + "message": "Website toevoegen" + }, + "deleteWebsite": { + "message": "Website verwijderen" + }, + "owner": { + "message": "Eigenaar" + }, + "addField": { + "message": "Veld toevoegen" + }, + "fieldType": { + "message": "Veldtype" + }, + "fieldLabel": { + "message": "Veldlabel" + }, + "add": { + "message": "Toevoegen" + }, + "textHelpText": { + "message": "Gebruik tekstvelden voor data zoals beveiligingsvragen" + }, + "hiddenHelpText": { + "message": "Gebruik verborgen velden voor gevoelige gegevens zoals een wachtwoord" + }, + "checkBoxHelpText": { + "message": "Gebruik aanvinkvakjes als je een formulier automatisch wilt invullen, zoals e-mailadres herinneren" + }, + "linkedHelpText": { + "message": "Gebruik een gekoppeld veld als je problemen ervaart met het automatisch invullen voor een specifieke website." + }, + "linkedLabelHelpText": { + "message": "Html-id, naam, aria-label of placeholder van het veld invullen." + }, "folder": { "message": "Map" }, @@ -418,6 +476,9 @@ "message": "Gekoppeld", "description": "This describes a field that is 'linked' (related) to another field." }, + "cfTypeCheckbox": { + "message": "Selectievakje" + }, "linkedValue": { "message": "Gekoppelde waarde", "description": "This describes a value that is 'linked' (related) to another value." @@ -1915,6 +1976,43 @@ } } }, + "cardDetails": { + "message": "Kaartgegevens" + }, + "cardBrandDetails": { + "message": "$BRAND$-gegevens", + "placeholders": { + "brand": { + "content": "$1", + "example": "Visa" + } + } + }, + "learnMoreAboutAuthenticators": { + "message": "Meer informatie over authenticatoren" + }, + "copyTOTP": { + "message": "Authenticatie-sleutel (TOTP) kopiëren" + }, + "totpHelperTitle": { + "message": "Maak tweestapsaanmelding naadloos" + }, + "totpHelper": { + "message": "Bitwarden kan tweestapsaanmeldingscodes opslaan en invullen. Kopieer en plak de sleutel in dit veld." + }, + "totpHelperWithCapture": { + "message": "Bitwarden kan tweestapsaanmeldingscodes opslaan en invullen. Selecteer het camerapictogram om een schermafbeelding van de QR-code van deze website te maken of kopieer en plak de sleutel in dit veld." + }, + "premium": { + "message": "Premium", + "description": "Premium membership" + }, + "cardExpiredTitle": { + "message": "Verlopen kaart" + }, + "cardExpiredMessage": { + "message": "Als je het hebt vernieuwd, werk dan de informatie van de kaart bij" + }, "verificationRequired": { "message": "Verificatie vereist", "description": "Default title for the user verification dialog." @@ -2084,6 +2182,15 @@ "personalOwnershipPolicyInEffectImports": { "message": "Organisatiebeleid heeft het importeren van items in je persoonlijke kluis geblokkeerd." }, + "personalDetails": { + "message": "Persoonlijke gegevens" + }, + "identification": { + "message": "Identificatie" + }, + "contactInfo": { + "message": "Contact informatie" + }, "allSends": { "message": "Alle Sends", "description": "'Sends' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." @@ -2518,6 +2625,9 @@ "generateEmail": { "message": "E-mailadres genereren" }, + "usernameGenerator": { + "message": "Gebruikersnaamgenerator" + }, "spinboxBoundariesHint": { "message": "Waarde moet tussen $MIN$ en $MAX$ liggen.", "description": "Explains spin box minimum and maximum values to the user", @@ -3212,6 +3322,9 @@ "followTheStepsBelowToFinishLoggingIn": { "message": "Volg de onderstaande stappen om in te loggen." }, + "followTheStepsBelowToFinishLoggingInWithSecurityKey": { + "message": "Volg onderstaande stappen om in te loggen met je beveiligingssleutel." + }, "launchDuo": { "message": "Start Duo in browser" }, @@ -3436,6 +3549,17 @@ "ssoError": { "message": "Er zijn geen vrije poorten gevonden voor de sso-login." }, + "securePasswordGenerated": { + "message": "Veilig wachtwoord aangemaakt! Vergeet niet om je wachtwoord ook op de website bij te werken." + }, + "useGeneratorHelpTextPartOne": { + "message": "Gebruik de generator", + "description": "This will be used as part of a larger sentence, broken up to include the generator icon. The full sentence will read 'Use the generator [GENERATOR_ICON] to create a strong unique password'" + }, + "useGeneratorHelpTextPartTwo": { + "message": "om een sterk uniek wachtwoord te maken", + "description": "This will be used as part of a larger sentence, broken up to include the generator icon. The full sentence will read 'Use the generator [GENERATOR_ICON] to create a strong unique password'" + }, "biometricsStatusHelptextUnlockNeeded": { "message": "Biometrisch ontgrendelen is niet beschikbaar omdat pincode of wachtwoordontgrendeling eerst vereist is." }, @@ -3514,6 +3638,27 @@ "setupTwoStepLogin": { "message": "Tweestapsaanmelding instellen" }, + "itemDetails": { + "message": "Itemdetails" + }, + "itemName": { + "message": "Itemnaam" + }, + "loginCredentials": { + "message": "Login referenties" + }, + "additionalOptions": { + "message": "Extra opties" + }, + "itemHistory": { + "message": "Itemgeschiedenis" + }, + "lastEdited": { + "message": "Laatst gewijzigd" + }, + "upload": { + "message": "Uploaden" + }, "newDeviceVerificationNoticeContentPage1": { "message": "Vanaf februari 2025 stuurt Bitwarden een code naar het e-mailadres van je account om inloggen op nieuwe apparaten te verifiëren." }, @@ -3567,5 +3712,35 @@ }, "move": { "message": "Verplaatsen" + }, + "newLoginNudgeTitle": { + "message": "Tijd besparen met automatisch aanvullen" + }, + "newLoginNudgeBody": { + "message": "Voeg een website toe zodat deze login wordt weergegeven als een automatische invulsuggestie." + }, + "newCardNudgeTitle": { + "message": "Naadloos online afrekenen" + }, + "newCardNudgeBody": { + "message": "Met kaarten gemakkelijk, veilig en accuraat automatisch invullen van betaalformulieren." + }, + "newIdentityNudgeTitle": { + "message": "Vereenvoudig het aanmaken van accounts" + }, + "newIdentityNudgeBody": { + "message": "Met identiteiten vul je lange registratie- of contactformulieren snel automatisch in." + }, + "newNoteNudgeTitle": { + "message": "Houd je gevoelige gegevens veilig" + }, + "newNoteNudgeBody": { + "message": "Met notities veilig opslaan van gevoelige gegevens zoals bank- of verzekeringsgegevens." + }, + "newSshNudgeTitle": { + "message": "Ontwikkelaars-vriendelijke SSH-toegang" + }, + "newSshNudgeBody": { + "message": "Sla je sleutels op en verbind met de SSH-agent voor snelle, versleutelde authenticatie." } } diff --git a/apps/desktop/src/locales/nn/messages.json b/apps/desktop/src/locales/nn/messages.json index 6e0c28ca5ae..894b505221b 100644 --- a/apps/desktop/src/locales/nn/messages.json +++ b/apps/desktop/src/locales/nn/messages.json @@ -393,6 +393,64 @@ "authenticatorKeyTotp": { "message": "Autentiseringsnykel (TOTP)" }, + "authenticatorKey": { + "message": "Authenticator key" + }, + "autofillOptions": { + "message": "Autofill options" + }, + "websiteUri": { + "message": "Website (URI)" + }, + "websiteUriCount": { + "message": "Website (URI) $COUNT$", + "description": "Label for an input field that contains a website URI. The input field is part of a list of fields, and the count indicates the position of the field in the list.", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + } + } + }, + "websiteAdded": { + "message": "Website added" + }, + "addWebsite": { + "message": "Add website" + }, + "deleteWebsite": { + "message": "Delete website" + }, + "owner": { + "message": "Owner" + }, + "addField": { + "message": "Add field" + }, + "fieldType": { + "message": "Field type" + }, + "fieldLabel": { + "message": "Field label" + }, + "add": { + "message": "Add" + }, + "textHelpText": { + "message": "Use text fields for data like security questions" + }, + "hiddenHelpText": { + "message": "Use hidden fields for sensitive data like a password" + }, + "checkBoxHelpText": { + "message": "Use checkboxes if you'd like to autofill a form's checkbox, like a remember email" + }, + "linkedHelpText": { + "message": "Use a linked field when you are experiencing autofill issues for a specific website." + }, + "linkedLabelHelpText": { + "message": "Enter the the field's html id, name, aria-label, or placeholder." + }, "folder": { "message": "Mappe" }, @@ -418,6 +476,9 @@ "message": "Tilknytt", "description": "This describes a field that is 'linked' (related) to another field." }, + "cfTypeCheckbox": { + "message": "Checkbox" + }, "linkedValue": { "message": "Tilknytt verdi", "description": "This describes a value that is 'linked' (related) to another value." @@ -1915,6 +1976,43 @@ } } }, + "cardDetails": { + "message": "Card details" + }, + "cardBrandDetails": { + "message": "$BRAND$ details", + "placeholders": { + "brand": { + "content": "$1", + "example": "Visa" + } + } + }, + "learnMoreAboutAuthenticators": { + "message": "Learn more about authenticators" + }, + "copyTOTP": { + "message": "Copy Authenticator key (TOTP)" + }, + "totpHelperTitle": { + "message": "Make 2-step verification seamless" + }, + "totpHelper": { + "message": "Bitwarden can store and fill 2-step verification codes. Copy and paste the key into this field." + }, + "totpHelperWithCapture": { + "message": "Bitwarden can store and fill 2-step verification codes. Select the camera icon to take a screenshot of this website's authenticator QR code, or copy and paste the key into this field." + }, + "premium": { + "message": "Premium", + "description": "Premium membership" + }, + "cardExpiredTitle": { + "message": "Expired card" + }, + "cardExpiredMessage": { + "message": "If you've renewed it, update the card's information" + }, "verificationRequired": { "message": "Verification required", "description": "Default title for the user verification dialog." @@ -2084,6 +2182,15 @@ "personalOwnershipPolicyInEffectImports": { "message": "An organization policy has blocked importing items into your individual vault." }, + "personalDetails": { + "message": "Personal details" + }, + "identification": { + "message": "Identification" + }, + "contactInfo": { + "message": "Contact information" + }, "allSends": { "message": "Alle Send-ar", "description": "'Sends' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." @@ -2518,6 +2625,9 @@ "generateEmail": { "message": "Generate email" }, + "usernameGenerator": { + "message": "Username generator" + }, "spinboxBoundariesHint": { "message": "Value must be between $MIN$ and $MAX$.", "description": "Explains spin box minimum and maximum values to the user", @@ -3212,6 +3322,9 @@ "followTheStepsBelowToFinishLoggingIn": { "message": "Follow the steps below to finish logging in." }, + "followTheStepsBelowToFinishLoggingInWithSecurityKey": { + "message": "Follow the steps below to finish logging in with your security key." + }, "launchDuo": { "message": "Launch Duo in Browser" }, @@ -3436,6 +3549,17 @@ "ssoError": { "message": "No free ports could be found for the sso login." }, + "securePasswordGenerated": { + "message": "Secure password generated! Don't forget to also update your password on the website." + }, + "useGeneratorHelpTextPartOne": { + "message": "Use the generator", + "description": "This will be used as part of a larger sentence, broken up to include the generator icon. The full sentence will read 'Use the generator [GENERATOR_ICON] to create a strong unique password'" + }, + "useGeneratorHelpTextPartTwo": { + "message": "to create a strong unique password", + "description": "This will be used as part of a larger sentence, broken up to include the generator icon. The full sentence will read 'Use the generator [GENERATOR_ICON] to create a strong unique password'" + }, "biometricsStatusHelptextUnlockNeeded": { "message": "Biometric unlock is unavailable because PIN or password unlock is required first." }, @@ -3514,6 +3638,27 @@ "setupTwoStepLogin": { "message": "Set up two-step login" }, + "itemDetails": { + "message": "Item details" + }, + "itemName": { + "message": "Item name" + }, + "loginCredentials": { + "message": "Login credentials" + }, + "additionalOptions": { + "message": "Additional options" + }, + "itemHistory": { + "message": "Item history" + }, + "lastEdited": { + "message": "Last edited" + }, + "upload": { + "message": "Upload" + }, "newDeviceVerificationNoticeContentPage1": { "message": "Bitwarden will send a code to your account email to verify logins from new devices starting in February 2025." }, @@ -3567,5 +3712,35 @@ }, "move": { "message": "Move" + }, + "newLoginNudgeTitle": { + "message": "Save time with autofill" + }, + "newLoginNudgeBody": { + "message": "Include a Website so this login appears as an autofill suggestion." + }, + "newCardNudgeTitle": { + "message": "Seamless online checkout" + }, + "newCardNudgeBody": { + "message": "With cards, easily autofill payment forms securely and accurately." + }, + "newIdentityNudgeTitle": { + "message": "Simplify creating accounts" + }, + "newIdentityNudgeBody": { + "message": "With identities, quickly autofill long registration or contact forms." + }, + "newNoteNudgeTitle": { + "message": "Keep your sensitive data safe" + }, + "newNoteNudgeBody": { + "message": "With notes, securely store sensitive data like banking or insurance details." + }, + "newSshNudgeTitle": { + "message": "Developer-friendly SSH access" + }, + "newSshNudgeBody": { + "message": "Store your keys and connect with the SSH agent for fast, encrypted authentication." } } diff --git a/apps/desktop/src/locales/or/messages.json b/apps/desktop/src/locales/or/messages.json index 790a5a7b15b..0e99fbcda3e 100644 --- a/apps/desktop/src/locales/or/messages.json +++ b/apps/desktop/src/locales/or/messages.json @@ -393,6 +393,64 @@ "authenticatorKeyTotp": { "message": "Authenticator key (TOTP)" }, + "authenticatorKey": { + "message": "Authenticator key" + }, + "autofillOptions": { + "message": "Autofill options" + }, + "websiteUri": { + "message": "Website (URI)" + }, + "websiteUriCount": { + "message": "Website (URI) $COUNT$", + "description": "Label for an input field that contains a website URI. The input field is part of a list of fields, and the count indicates the position of the field in the list.", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + } + } + }, + "websiteAdded": { + "message": "Website added" + }, + "addWebsite": { + "message": "Add website" + }, + "deleteWebsite": { + "message": "Delete website" + }, + "owner": { + "message": "Owner" + }, + "addField": { + "message": "Add field" + }, + "fieldType": { + "message": "Field type" + }, + "fieldLabel": { + "message": "Field label" + }, + "add": { + "message": "Add" + }, + "textHelpText": { + "message": "Use text fields for data like security questions" + }, + "hiddenHelpText": { + "message": "Use hidden fields for sensitive data like a password" + }, + "checkBoxHelpText": { + "message": "Use checkboxes if you'd like to autofill a form's checkbox, like a remember email" + }, + "linkedHelpText": { + "message": "Use a linked field when you are experiencing autofill issues for a specific website." + }, + "linkedLabelHelpText": { + "message": "Enter the the field's html id, name, aria-label, or placeholder." + }, "folder": { "message": "ଫୋଲ୍ଡର୍" }, @@ -418,6 +476,9 @@ "message": "Linked", "description": "This describes a field that is 'linked' (related) to another field." }, + "cfTypeCheckbox": { + "message": "Checkbox" + }, "linkedValue": { "message": "Linked value", "description": "This describes a value that is 'linked' (related) to another value." @@ -1915,6 +1976,43 @@ } } }, + "cardDetails": { + "message": "Card details" + }, + "cardBrandDetails": { + "message": "$BRAND$ details", + "placeholders": { + "brand": { + "content": "$1", + "example": "Visa" + } + } + }, + "learnMoreAboutAuthenticators": { + "message": "Learn more about authenticators" + }, + "copyTOTP": { + "message": "Copy Authenticator key (TOTP)" + }, + "totpHelperTitle": { + "message": "Make 2-step verification seamless" + }, + "totpHelper": { + "message": "Bitwarden can store and fill 2-step verification codes. Copy and paste the key into this field." + }, + "totpHelperWithCapture": { + "message": "Bitwarden can store and fill 2-step verification codes. Select the camera icon to take a screenshot of this website's authenticator QR code, or copy and paste the key into this field." + }, + "premium": { + "message": "Premium", + "description": "Premium membership" + }, + "cardExpiredTitle": { + "message": "Expired card" + }, + "cardExpiredMessage": { + "message": "If you've renewed it, update the card's information" + }, "verificationRequired": { "message": "Verification required", "description": "Default title for the user verification dialog." @@ -2084,6 +2182,15 @@ "personalOwnershipPolicyInEffectImports": { "message": "An organization policy has blocked importing items into your individual vault." }, + "personalDetails": { + "message": "Personal details" + }, + "identification": { + "message": "Identification" + }, + "contactInfo": { + "message": "Contact information" + }, "allSends": { "message": "All Sends", "description": "'Sends' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." @@ -2518,6 +2625,9 @@ "generateEmail": { "message": "Generate email" }, + "usernameGenerator": { + "message": "Username generator" + }, "spinboxBoundariesHint": { "message": "Value must be between $MIN$ and $MAX$.", "description": "Explains spin box minimum and maximum values to the user", @@ -3212,6 +3322,9 @@ "followTheStepsBelowToFinishLoggingIn": { "message": "Follow the steps below to finish logging in." }, + "followTheStepsBelowToFinishLoggingInWithSecurityKey": { + "message": "Follow the steps below to finish logging in with your security key." + }, "launchDuo": { "message": "Launch Duo in Browser" }, @@ -3436,6 +3549,17 @@ "ssoError": { "message": "No free ports could be found for the sso login." }, + "securePasswordGenerated": { + "message": "Secure password generated! Don't forget to also update your password on the website." + }, + "useGeneratorHelpTextPartOne": { + "message": "Use the generator", + "description": "This will be used as part of a larger sentence, broken up to include the generator icon. The full sentence will read 'Use the generator [GENERATOR_ICON] to create a strong unique password'" + }, + "useGeneratorHelpTextPartTwo": { + "message": "to create a strong unique password", + "description": "This will be used as part of a larger sentence, broken up to include the generator icon. The full sentence will read 'Use the generator [GENERATOR_ICON] to create a strong unique password'" + }, "biometricsStatusHelptextUnlockNeeded": { "message": "Biometric unlock is unavailable because PIN or password unlock is required first." }, @@ -3514,6 +3638,27 @@ "setupTwoStepLogin": { "message": "Set up two-step login" }, + "itemDetails": { + "message": "Item details" + }, + "itemName": { + "message": "Item name" + }, + "loginCredentials": { + "message": "Login credentials" + }, + "additionalOptions": { + "message": "Additional options" + }, + "itemHistory": { + "message": "Item history" + }, + "lastEdited": { + "message": "Last edited" + }, + "upload": { + "message": "Upload" + }, "newDeviceVerificationNoticeContentPage1": { "message": "Bitwarden will send a code to your account email to verify logins from new devices starting in February 2025." }, @@ -3567,5 +3712,35 @@ }, "move": { "message": "Move" + }, + "newLoginNudgeTitle": { + "message": "Save time with autofill" + }, + "newLoginNudgeBody": { + "message": "Include a Website so this login appears as an autofill suggestion." + }, + "newCardNudgeTitle": { + "message": "Seamless online checkout" + }, + "newCardNudgeBody": { + "message": "With cards, easily autofill payment forms securely and accurately." + }, + "newIdentityNudgeTitle": { + "message": "Simplify creating accounts" + }, + "newIdentityNudgeBody": { + "message": "With identities, quickly autofill long registration or contact forms." + }, + "newNoteNudgeTitle": { + "message": "Keep your sensitive data safe" + }, + "newNoteNudgeBody": { + "message": "With notes, securely store sensitive data like banking or insurance details." + }, + "newSshNudgeTitle": { + "message": "Developer-friendly SSH access" + }, + "newSshNudgeBody": { + "message": "Store your keys and connect with the SSH agent for fast, encrypted authentication." } } diff --git a/apps/desktop/src/locales/pl/messages.json b/apps/desktop/src/locales/pl/messages.json index 1b36128cc68..c6000b84e7a 100644 --- a/apps/desktop/src/locales/pl/messages.json +++ b/apps/desktop/src/locales/pl/messages.json @@ -393,6 +393,64 @@ "authenticatorKeyTotp": { "message": "Klucz uwierzytelniający (TOTP)" }, + "authenticatorKey": { + "message": "Authenticator key" + }, + "autofillOptions": { + "message": "Autofill options" + }, + "websiteUri": { + "message": "Website (URI)" + }, + "websiteUriCount": { + "message": "Website (URI) $COUNT$", + "description": "Label for an input field that contains a website URI. The input field is part of a list of fields, and the count indicates the position of the field in the list.", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + } + } + }, + "websiteAdded": { + "message": "Website added" + }, + "addWebsite": { + "message": "Add website" + }, + "deleteWebsite": { + "message": "Delete website" + }, + "owner": { + "message": "Owner" + }, + "addField": { + "message": "Add field" + }, + "fieldType": { + "message": "Field type" + }, + "fieldLabel": { + "message": "Field label" + }, + "add": { + "message": "Add" + }, + "textHelpText": { + "message": "Use text fields for data like security questions" + }, + "hiddenHelpText": { + "message": "Use hidden fields for sensitive data like a password" + }, + "checkBoxHelpText": { + "message": "Use checkboxes if you'd like to autofill a form's checkbox, like a remember email" + }, + "linkedHelpText": { + "message": "Use a linked field when you are experiencing autofill issues for a specific website." + }, + "linkedLabelHelpText": { + "message": "Enter the the field's html id, name, aria-label, or placeholder." + }, "folder": { "message": "Folder" }, @@ -418,6 +476,9 @@ "message": "Powiązane pole", "description": "This describes a field that is 'linked' (related) to another field." }, + "cfTypeCheckbox": { + "message": "Checkbox" + }, "linkedValue": { "message": "Powiązana wartość", "description": "This describes a value that is 'linked' (related) to another value." @@ -1915,6 +1976,43 @@ } } }, + "cardDetails": { + "message": "Card details" + }, + "cardBrandDetails": { + "message": "$BRAND$ details", + "placeholders": { + "brand": { + "content": "$1", + "example": "Visa" + } + } + }, + "learnMoreAboutAuthenticators": { + "message": "Learn more about authenticators" + }, + "copyTOTP": { + "message": "Copy Authenticator key (TOTP)" + }, + "totpHelperTitle": { + "message": "Make 2-step verification seamless" + }, + "totpHelper": { + "message": "Bitwarden can store and fill 2-step verification codes. Copy and paste the key into this field." + }, + "totpHelperWithCapture": { + "message": "Bitwarden can store and fill 2-step verification codes. Select the camera icon to take a screenshot of this website's authenticator QR code, or copy and paste the key into this field." + }, + "premium": { + "message": "Premium", + "description": "Premium membership" + }, + "cardExpiredTitle": { + "message": "Expired card" + }, + "cardExpiredMessage": { + "message": "If you've renewed it, update the card's information" + }, "verificationRequired": { "message": "Wymagana weryfikacja", "description": "Default title for the user verification dialog." @@ -2084,6 +2182,15 @@ "personalOwnershipPolicyInEffectImports": { "message": "Polityka organizacji zablokowała importowanie elementów do Twojego sejfu." }, + "personalDetails": { + "message": "Personal details" + }, + "identification": { + "message": "Identification" + }, + "contactInfo": { + "message": "Contact information" + }, "allSends": { "message": "Wszystkie wysyłki", "description": "'Sends' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." @@ -2518,6 +2625,9 @@ "generateEmail": { "message": "Wygeneruj e-mail" }, + "usernameGenerator": { + "message": "Username generator" + }, "spinboxBoundariesHint": { "message": "Wartość musi być pomiędzy $MIN$ a $MAX$.", "description": "Explains spin box minimum and maximum values to the user", @@ -3212,6 +3322,9 @@ "followTheStepsBelowToFinishLoggingIn": { "message": "Wykonaj poniższe kroki, by dokończyć logowanie" }, + "followTheStepsBelowToFinishLoggingInWithSecurityKey": { + "message": "Follow the steps below to finish logging in with your security key." + }, "launchDuo": { "message": "Uruchom Duo w przeglądarce" }, @@ -3436,6 +3549,17 @@ "ssoError": { "message": "Nie znaleziono wolnych portów dla logowania SSO." }, + "securePasswordGenerated": { + "message": "Secure password generated! Don't forget to also update your password on the website." + }, + "useGeneratorHelpTextPartOne": { + "message": "Use the generator", + "description": "This will be used as part of a larger sentence, broken up to include the generator icon. The full sentence will read 'Use the generator [GENERATOR_ICON] to create a strong unique password'" + }, + "useGeneratorHelpTextPartTwo": { + "message": "to create a strong unique password", + "description": "This will be used as part of a larger sentence, broken up to include the generator icon. The full sentence will read 'Use the generator [GENERATOR_ICON] to create a strong unique password'" + }, "biometricsStatusHelptextUnlockNeeded": { "message": "Odblokowanie odciskiem palca jest niedostępne, ponieważ najpierw wymagane jest odblokowanie kodem PIN lub hasłem." }, @@ -3514,6 +3638,27 @@ "setupTwoStepLogin": { "message": "Skonfiguruj dwustopniowe logowanie" }, + "itemDetails": { + "message": "Item details" + }, + "itemName": { + "message": "Item name" + }, + "loginCredentials": { + "message": "Login credentials" + }, + "additionalOptions": { + "message": "Additional options" + }, + "itemHistory": { + "message": "Item history" + }, + "lastEdited": { + "message": "Last edited" + }, + "upload": { + "message": "Upload" + }, "newDeviceVerificationNoticeContentPage1": { "message": "Bitwarden wyśle kod na Twój adres e-mail w celu zweryfikowania logowania z nowych urządzeń, począwszy od lutego 2025 r." }, @@ -3567,5 +3712,35 @@ }, "move": { "message": "Move" + }, + "newLoginNudgeTitle": { + "message": "Save time with autofill" + }, + "newLoginNudgeBody": { + "message": "Include a Website so this login appears as an autofill suggestion." + }, + "newCardNudgeTitle": { + "message": "Seamless online checkout" + }, + "newCardNudgeBody": { + "message": "With cards, easily autofill payment forms securely and accurately." + }, + "newIdentityNudgeTitle": { + "message": "Simplify creating accounts" + }, + "newIdentityNudgeBody": { + "message": "With identities, quickly autofill long registration or contact forms." + }, + "newNoteNudgeTitle": { + "message": "Keep your sensitive data safe" + }, + "newNoteNudgeBody": { + "message": "With notes, securely store sensitive data like banking or insurance details." + }, + "newSshNudgeTitle": { + "message": "Developer-friendly SSH access" + }, + "newSshNudgeBody": { + "message": "Store your keys and connect with the SSH agent for fast, encrypted authentication." } } diff --git a/apps/desktop/src/locales/pt_BR/messages.json b/apps/desktop/src/locales/pt_BR/messages.json index 5dcb648f5a3..35d63e07b1a 100644 --- a/apps/desktop/src/locales/pt_BR/messages.json +++ b/apps/desktop/src/locales/pt_BR/messages.json @@ -393,6 +393,64 @@ "authenticatorKeyTotp": { "message": "Chave de Autenticação (TOTP)" }, + "authenticatorKey": { + "message": "Authenticator key" + }, + "autofillOptions": { + "message": "Autofill options" + }, + "websiteUri": { + "message": "Website (URI)" + }, + "websiteUriCount": { + "message": "Website (URI) $COUNT$", + "description": "Label for an input field that contains a website URI. The input field is part of a list of fields, and the count indicates the position of the field in the list.", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + } + } + }, + "websiteAdded": { + "message": "Website added" + }, + "addWebsite": { + "message": "Add website" + }, + "deleteWebsite": { + "message": "Delete website" + }, + "owner": { + "message": "Owner" + }, + "addField": { + "message": "Add field" + }, + "fieldType": { + "message": "Field type" + }, + "fieldLabel": { + "message": "Field label" + }, + "add": { + "message": "Add" + }, + "textHelpText": { + "message": "Use text fields for data like security questions" + }, + "hiddenHelpText": { + "message": "Use hidden fields for sensitive data like a password" + }, + "checkBoxHelpText": { + "message": "Use checkboxes if you'd like to autofill a form's checkbox, like a remember email" + }, + "linkedHelpText": { + "message": "Use a linked field when you are experiencing autofill issues for a specific website." + }, + "linkedLabelHelpText": { + "message": "Enter the the field's html id, name, aria-label, or placeholder." + }, "folder": { "message": "Pasta" }, @@ -418,6 +476,9 @@ "message": "Vinculado", "description": "This describes a field that is 'linked' (related) to another field." }, + "cfTypeCheckbox": { + "message": "Checkbox" + }, "linkedValue": { "message": "Valor vinculado", "description": "This describes a value that is 'linked' (related) to another value." @@ -1915,6 +1976,43 @@ } } }, + "cardDetails": { + "message": "Card details" + }, + "cardBrandDetails": { + "message": "$BRAND$ details", + "placeholders": { + "brand": { + "content": "$1", + "example": "Visa" + } + } + }, + "learnMoreAboutAuthenticators": { + "message": "Learn more about authenticators" + }, + "copyTOTP": { + "message": "Copy Authenticator key (TOTP)" + }, + "totpHelperTitle": { + "message": "Make 2-step verification seamless" + }, + "totpHelper": { + "message": "Bitwarden can store and fill 2-step verification codes. Copy and paste the key into this field." + }, + "totpHelperWithCapture": { + "message": "Bitwarden can store and fill 2-step verification codes. Select the camera icon to take a screenshot of this website's authenticator QR code, or copy and paste the key into this field." + }, + "premium": { + "message": "Premium", + "description": "Premium membership" + }, + "cardExpiredTitle": { + "message": "Expired card" + }, + "cardExpiredMessage": { + "message": "If you've renewed it, update the card's information" + }, "verificationRequired": { "message": "Verificação necessária", "description": "Default title for the user verification dialog." @@ -2084,6 +2182,15 @@ "personalOwnershipPolicyInEffectImports": { "message": "A política da organização bloqueou a importação de itens para o seu cofre." }, + "personalDetails": { + "message": "Personal details" + }, + "identification": { + "message": "Identification" + }, + "contactInfo": { + "message": "Contact information" + }, "allSends": { "message": "Todos os Sends", "description": "'Sends' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." @@ -2518,6 +2625,9 @@ "generateEmail": { "message": "Gerar e-mail" }, + "usernameGenerator": { + "message": "Username generator" + }, "spinboxBoundariesHint": { "message": "Valor deve ser entre $MIN$ e $MAX$.", "description": "Explains spin box minimum and maximum values to the user", @@ -3212,6 +3322,9 @@ "followTheStepsBelowToFinishLoggingIn": { "message": "Siga os passos abaixo para finalizar o login." }, + "followTheStepsBelowToFinishLoggingInWithSecurityKey": { + "message": "Follow the steps below to finish logging in with your security key." + }, "launchDuo": { "message": "Iniciar o Duo no navegador" }, @@ -3436,6 +3549,17 @@ "ssoError": { "message": "Nenhuma porta livre foi encontrada para o cliente final." }, + "securePasswordGenerated": { + "message": "Secure password generated! Don't forget to also update your password on the website." + }, + "useGeneratorHelpTextPartOne": { + "message": "Use the generator", + "description": "This will be used as part of a larger sentence, broken up to include the generator icon. The full sentence will read 'Use the generator [GENERATOR_ICON] to create a strong unique password'" + }, + "useGeneratorHelpTextPartTwo": { + "message": "to create a strong unique password", + "description": "This will be used as part of a larger sentence, broken up to include the generator icon. The full sentence will read 'Use the generator [GENERATOR_ICON] to create a strong unique password'" + }, "biometricsStatusHelptextUnlockNeeded": { "message": "O desbloqueio por biometria está indisponível, pois é necessário informar o PIN ou a senha primeiro." }, @@ -3514,6 +3638,27 @@ "setupTwoStepLogin": { "message": "Configurar login em duas etapas" }, + "itemDetails": { + "message": "Item details" + }, + "itemName": { + "message": "Item name" + }, + "loginCredentials": { + "message": "Login credentials" + }, + "additionalOptions": { + "message": "Additional options" + }, + "itemHistory": { + "message": "Item history" + }, + "lastEdited": { + "message": "Last edited" + }, + "upload": { + "message": "Upload" + }, "newDeviceVerificationNoticeContentPage1": { "message": "Bitwarden enviará um código para o seu e-mail para verificar novos dispositivos a partir de fevereiro de 2025." }, @@ -3567,5 +3712,35 @@ }, "move": { "message": "Mover" + }, + "newLoginNudgeTitle": { + "message": "Save time with autofill" + }, + "newLoginNudgeBody": { + "message": "Include a Website so this login appears as an autofill suggestion." + }, + "newCardNudgeTitle": { + "message": "Seamless online checkout" + }, + "newCardNudgeBody": { + "message": "With cards, easily autofill payment forms securely and accurately." + }, + "newIdentityNudgeTitle": { + "message": "Simplify creating accounts" + }, + "newIdentityNudgeBody": { + "message": "With identities, quickly autofill long registration or contact forms." + }, + "newNoteNudgeTitle": { + "message": "Keep your sensitive data safe" + }, + "newNoteNudgeBody": { + "message": "With notes, securely store sensitive data like banking or insurance details." + }, + "newSshNudgeTitle": { + "message": "Developer-friendly SSH access" + }, + "newSshNudgeBody": { + "message": "Store your keys and connect with the SSH agent for fast, encrypted authentication." } } diff --git a/apps/desktop/src/locales/pt_PT/messages.json b/apps/desktop/src/locales/pt_PT/messages.json index 2a1f1613b92..50163e811ba 100644 --- a/apps/desktop/src/locales/pt_PT/messages.json +++ b/apps/desktop/src/locales/pt_PT/messages.json @@ -393,6 +393,64 @@ "authenticatorKeyTotp": { "message": "Chave de autenticação (TOTP)" }, + "authenticatorKey": { + "message": "Chave de autenticação" + }, + "autofillOptions": { + "message": "Opções de preenchimento automático" + }, + "websiteUri": { + "message": "Site (URI)" + }, + "websiteUriCount": { + "message": "Site (URI) $COUNT$", + "description": "Label for an input field that contains a website URI. The input field is part of a list of fields, and the count indicates the position of the field in the list.", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + } + } + }, + "websiteAdded": { + "message": "Site adicionado" + }, + "addWebsite": { + "message": "Adicionar site" + }, + "deleteWebsite": { + "message": "Eliminar site" + }, + "owner": { + "message": "Proprietário" + }, + "addField": { + "message": "Adicionar campo" + }, + "fieldType": { + "message": "Tipo de campo" + }, + "fieldLabel": { + "message": "Etiqueta do campo" + }, + "add": { + "message": "Adicionar" + }, + "textHelpText": { + "message": "Utilize campos de texto para dados como perguntas de segurança" + }, + "hiddenHelpText": { + "message": "Utilize campos ocultos para dados sensíveis como uma palavra-passe" + }, + "checkBoxHelpText": { + "message": "Utilize caixas de verificação se pretender preencher automaticamente uma caixa de verificação de um formulário, como um e-mail de memorização" + }, + "linkedHelpText": { + "message": "Utilize um campo ligado quando tiver problemas de preenchimento automático para um site específico." + }, + "linkedLabelHelpText": { + "message": "Introduza o ID do HTML, o nome, a aria-label ou o placeholder do campo." + }, "folder": { "message": "Pasta" }, @@ -418,6 +476,9 @@ "message": "Associado", "description": "This describes a field that is 'linked' (related) to another field." }, + "cfTypeCheckbox": { + "message": "Caixa de verificação" + }, "linkedValue": { "message": "Valor associado", "description": "This describes a value that is 'linked' (related) to another value." @@ -1915,6 +1976,43 @@ } } }, + "cardDetails": { + "message": "Detalhes do cartão" + }, + "cardBrandDetails": { + "message": "Detalhes do $BRAND$", + "placeholders": { + "brand": { + "content": "$1", + "example": "Visa" + } + } + }, + "learnMoreAboutAuthenticators": { + "message": "Saiba mais sobre os autenticadores" + }, + "copyTOTP": { + "message": "Copiar Chave de autenticação (TOTP)" + }, + "totpHelperTitle": { + "message": "Torne a verificação de dois passos simples" + }, + "totpHelper": { + "message": "O Bitwarden pode armazenar e preencher códigos de verificação de dois passos. Copie e cole a chave neste campo." + }, + "totpHelperWithCapture": { + "message": "O Bitwarden pode armazenar e preencher códigos de verificação de dois passos. Selecione o ícone da câmara para tirar uma captura de ecrã do código QR do autenticador deste site ou copie e cole a chave neste campo." + }, + "premium": { + "message": "Premium", + "description": "Premium membership" + }, + "cardExpiredTitle": { + "message": "Cartão expirado" + }, + "cardExpiredMessage": { + "message": "Se o renovou, atualize as informações do cartão" + }, "verificationRequired": { "message": "Verificação necessária", "description": "Default title for the user verification dialog." @@ -2084,6 +2182,15 @@ "personalOwnershipPolicyInEffectImports": { "message": "Uma política da organização bloqueou a importação de itens para o seu cofre individual." }, + "personalDetails": { + "message": "Dados pessoais" + }, + "identification": { + "message": "Identificação" + }, + "contactInfo": { + "message": "Informações de contacto" + }, "allSends": { "message": "Todos os Sends", "description": "'Sends' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." @@ -2518,6 +2625,9 @@ "generateEmail": { "message": "Gerar e-mail" }, + "usernameGenerator": { + "message": "Gerador de nomes de utilizador" + }, "spinboxBoundariesHint": { "message": "O valor deve estar entre $MIN$ e $MAX$.", "description": "Explains spin box minimum and maximum values to the user", @@ -3212,6 +3322,9 @@ "followTheStepsBelowToFinishLoggingIn": { "message": "Siga os passos abaixo para concluir o início de sessão." }, + "followTheStepsBelowToFinishLoggingInWithSecurityKey": { + "message": "Siga os passos abaixo para concluir o início de sessão com a sua chave de segurança." + }, "launchDuo": { "message": "Iniciar o Duo no navegador" }, @@ -3436,6 +3549,17 @@ "ssoError": { "message": "Não foi possível encontrar portas livres para o início de sessão sso." }, + "securePasswordGenerated": { + "message": "Palavra-passe segura gerada! Não se esqueça de atualizar também a sua palavra-passe no site." + }, + "useGeneratorHelpTextPartOne": { + "message": "Utilize o gerador", + "description": "This will be used as part of a larger sentence, broken up to include the generator icon. The full sentence will read 'Use the generator [GENERATOR_ICON] to create a strong unique password'" + }, + "useGeneratorHelpTextPartTwo": { + "message": "para criar uma palavra-passe forte e única", + "description": "This will be used as part of a larger sentence, broken up to include the generator icon. The full sentence will read 'Use the generator [GENERATOR_ICON] to create a strong unique password'" + }, "biometricsStatusHelptextUnlockNeeded": { "message": "O desbloqueio biométrico não está disponível porque o desbloqueio por PIN ou palavra-passe é necessário primeiro." }, @@ -3514,6 +3638,27 @@ "setupTwoStepLogin": { "message": "Definir a verificação de dois passos" }, + "itemDetails": { + "message": "Detalhes do item" + }, + "itemName": { + "message": "Nome do item" + }, + "loginCredentials": { + "message": "Credenciais de início de sessão" + }, + "additionalOptions": { + "message": "Opções adicionais" + }, + "itemHistory": { + "message": "Histórico do item" + }, + "lastEdited": { + "message": "Última edição" + }, + "upload": { + "message": "Carregar" + }, "newDeviceVerificationNoticeContentPage1": { "message": "O Bitwarden enviará um código para o e-mail da sua conta para verificar as credenciais de novos dispositivos a partir de fevereiro de 2025." }, @@ -3567,5 +3712,35 @@ }, "move": { "message": "Mover" + }, + "newLoginNudgeTitle": { + "message": "Poupe tempo com o preenchimento automático" + }, + "newLoginNudgeBody": { + "message": "Inclua um site para que esta credencial apareça como uma sugestão de preenchimento automático." + }, + "newCardNudgeTitle": { + "message": "Pagamentos online sem problemas" + }, + "newCardNudgeBody": { + "message": "Com os cartões, preencha facilmente formulários de pagamento de forma segura e exata." + }, + "newIdentityNudgeTitle": { + "message": "Simplifique a criação de contas" + }, + "newIdentityNudgeBody": { + "message": "Com as identidades, preencha rapidamente formulários de registo ou de contacto longos." + }, + "newNoteNudgeTitle": { + "message": "Mantenha os seus dados sensíveis seguros" + }, + "newNoteNudgeBody": { + "message": "Com as notas, armazene de forma segura dados sensíveis, como dados bancários ou de seguros." + }, + "newSshNudgeTitle": { + "message": "Acesso SSH de fácil utilização pelos programadores" + }, + "newSshNudgeBody": { + "message": "Guarde as suas chaves e ligue-se ao agente SSH para uma autenticação rápida e encriptada." } } diff --git a/apps/desktop/src/locales/ro/messages.json b/apps/desktop/src/locales/ro/messages.json index 5ff57f0e3ec..80b87b73f50 100644 --- a/apps/desktop/src/locales/ro/messages.json +++ b/apps/desktop/src/locales/ro/messages.json @@ -393,6 +393,64 @@ "authenticatorKeyTotp": { "message": "Cheie de autentificare (TOTP)" }, + "authenticatorKey": { + "message": "Authenticator key" + }, + "autofillOptions": { + "message": "Autofill options" + }, + "websiteUri": { + "message": "Website (URI)" + }, + "websiteUriCount": { + "message": "Website (URI) $COUNT$", + "description": "Label for an input field that contains a website URI. The input field is part of a list of fields, and the count indicates the position of the field in the list.", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + } + } + }, + "websiteAdded": { + "message": "Website added" + }, + "addWebsite": { + "message": "Add website" + }, + "deleteWebsite": { + "message": "Delete website" + }, + "owner": { + "message": "Owner" + }, + "addField": { + "message": "Add field" + }, + "fieldType": { + "message": "Field type" + }, + "fieldLabel": { + "message": "Field label" + }, + "add": { + "message": "Add" + }, + "textHelpText": { + "message": "Use text fields for data like security questions" + }, + "hiddenHelpText": { + "message": "Use hidden fields for sensitive data like a password" + }, + "checkBoxHelpText": { + "message": "Use checkboxes if you'd like to autofill a form's checkbox, like a remember email" + }, + "linkedHelpText": { + "message": "Use a linked field when you are experiencing autofill issues for a specific website." + }, + "linkedLabelHelpText": { + "message": "Enter the the field's html id, name, aria-label, or placeholder." + }, "folder": { "message": "Dosar" }, @@ -418,6 +476,9 @@ "message": "Conectat", "description": "This describes a field that is 'linked' (related) to another field." }, + "cfTypeCheckbox": { + "message": "Checkbox" + }, "linkedValue": { "message": "Valoarea conectată", "description": "This describes a value that is 'linked' (related) to another value." @@ -1915,6 +1976,43 @@ } } }, + "cardDetails": { + "message": "Card details" + }, + "cardBrandDetails": { + "message": "$BRAND$ details", + "placeholders": { + "brand": { + "content": "$1", + "example": "Visa" + } + } + }, + "learnMoreAboutAuthenticators": { + "message": "Learn more about authenticators" + }, + "copyTOTP": { + "message": "Copy Authenticator key (TOTP)" + }, + "totpHelperTitle": { + "message": "Make 2-step verification seamless" + }, + "totpHelper": { + "message": "Bitwarden can store and fill 2-step verification codes. Copy and paste the key into this field." + }, + "totpHelperWithCapture": { + "message": "Bitwarden can store and fill 2-step verification codes. Select the camera icon to take a screenshot of this website's authenticator QR code, or copy and paste the key into this field." + }, + "premium": { + "message": "Premium", + "description": "Premium membership" + }, + "cardExpiredTitle": { + "message": "Expired card" + }, + "cardExpiredMessage": { + "message": "If you've renewed it, update the card's information" + }, "verificationRequired": { "message": "Verification required", "description": "Default title for the user verification dialog." @@ -2084,6 +2182,15 @@ "personalOwnershipPolicyInEffectImports": { "message": "An organization policy has blocked importing items into your individual vault." }, + "personalDetails": { + "message": "Personal details" + }, + "identification": { + "message": "Identification" + }, + "contactInfo": { + "message": "Contact information" + }, "allSends": { "message": "Toate Send-urile", "description": "'Sends' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." @@ -2518,6 +2625,9 @@ "generateEmail": { "message": "Generate email" }, + "usernameGenerator": { + "message": "Username generator" + }, "spinboxBoundariesHint": { "message": "Value must be between $MIN$ and $MAX$.", "description": "Explains spin box minimum and maximum values to the user", @@ -3212,6 +3322,9 @@ "followTheStepsBelowToFinishLoggingIn": { "message": "Follow the steps below to finish logging in." }, + "followTheStepsBelowToFinishLoggingInWithSecurityKey": { + "message": "Follow the steps below to finish logging in with your security key." + }, "launchDuo": { "message": "Launch Duo in Browser" }, @@ -3436,6 +3549,17 @@ "ssoError": { "message": "No free ports could be found for the sso login." }, + "securePasswordGenerated": { + "message": "Secure password generated! Don't forget to also update your password on the website." + }, + "useGeneratorHelpTextPartOne": { + "message": "Use the generator", + "description": "This will be used as part of a larger sentence, broken up to include the generator icon. The full sentence will read 'Use the generator [GENERATOR_ICON] to create a strong unique password'" + }, + "useGeneratorHelpTextPartTwo": { + "message": "to create a strong unique password", + "description": "This will be used as part of a larger sentence, broken up to include the generator icon. The full sentence will read 'Use the generator [GENERATOR_ICON] to create a strong unique password'" + }, "biometricsStatusHelptextUnlockNeeded": { "message": "Biometric unlock is unavailable because PIN or password unlock is required first." }, @@ -3514,6 +3638,27 @@ "setupTwoStepLogin": { "message": "Set up two-step login" }, + "itemDetails": { + "message": "Item details" + }, + "itemName": { + "message": "Item name" + }, + "loginCredentials": { + "message": "Login credentials" + }, + "additionalOptions": { + "message": "Additional options" + }, + "itemHistory": { + "message": "Item history" + }, + "lastEdited": { + "message": "Last edited" + }, + "upload": { + "message": "Upload" + }, "newDeviceVerificationNoticeContentPage1": { "message": "Bitwarden will send a code to your account email to verify logins from new devices starting in February 2025." }, @@ -3567,5 +3712,35 @@ }, "move": { "message": "Move" + }, + "newLoginNudgeTitle": { + "message": "Save time with autofill" + }, + "newLoginNudgeBody": { + "message": "Include a Website so this login appears as an autofill suggestion." + }, + "newCardNudgeTitle": { + "message": "Seamless online checkout" + }, + "newCardNudgeBody": { + "message": "With cards, easily autofill payment forms securely and accurately." + }, + "newIdentityNudgeTitle": { + "message": "Simplify creating accounts" + }, + "newIdentityNudgeBody": { + "message": "With identities, quickly autofill long registration or contact forms." + }, + "newNoteNudgeTitle": { + "message": "Keep your sensitive data safe" + }, + "newNoteNudgeBody": { + "message": "With notes, securely store sensitive data like banking or insurance details." + }, + "newSshNudgeTitle": { + "message": "Developer-friendly SSH access" + }, + "newSshNudgeBody": { + "message": "Store your keys and connect with the SSH agent for fast, encrypted authentication." } } diff --git a/apps/desktop/src/locales/ru/messages.json b/apps/desktop/src/locales/ru/messages.json index 870efba3c9e..b1f3c35e9be 100644 --- a/apps/desktop/src/locales/ru/messages.json +++ b/apps/desktop/src/locales/ru/messages.json @@ -393,6 +393,64 @@ "authenticatorKeyTotp": { "message": "Ключ аутентификатора (TOTP)" }, + "authenticatorKey": { + "message": "Authenticator key" + }, + "autofillOptions": { + "message": "Autofill options" + }, + "websiteUri": { + "message": "Website (URI)" + }, + "websiteUriCount": { + "message": "Website (URI) $COUNT$", + "description": "Label for an input field that contains a website URI. The input field is part of a list of fields, and the count indicates the position of the field in the list.", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + } + } + }, + "websiteAdded": { + "message": "Website added" + }, + "addWebsite": { + "message": "Add website" + }, + "deleteWebsite": { + "message": "Delete website" + }, + "owner": { + "message": "Owner" + }, + "addField": { + "message": "Add field" + }, + "fieldType": { + "message": "Field type" + }, + "fieldLabel": { + "message": "Field label" + }, + "add": { + "message": "Add" + }, + "textHelpText": { + "message": "Use text fields for data like security questions" + }, + "hiddenHelpText": { + "message": "Use hidden fields for sensitive data like a password" + }, + "checkBoxHelpText": { + "message": "Use checkboxes if you'd like to autofill a form's checkbox, like a remember email" + }, + "linkedHelpText": { + "message": "Use a linked field when you are experiencing autofill issues for a specific website." + }, + "linkedLabelHelpText": { + "message": "Enter the the field's html id, name, aria-label, or placeholder." + }, "folder": { "message": "Папка" }, @@ -418,6 +476,9 @@ "message": "Связано", "description": "This describes a field that is 'linked' (related) to another field." }, + "cfTypeCheckbox": { + "message": "Checkbox" + }, "linkedValue": { "message": "Связанное значение", "description": "This describes a value that is 'linked' (related) to another value." @@ -1915,6 +1976,43 @@ } } }, + "cardDetails": { + "message": "Card details" + }, + "cardBrandDetails": { + "message": "$BRAND$ details", + "placeholders": { + "brand": { + "content": "$1", + "example": "Visa" + } + } + }, + "learnMoreAboutAuthenticators": { + "message": "Learn more about authenticators" + }, + "copyTOTP": { + "message": "Copy Authenticator key (TOTP)" + }, + "totpHelperTitle": { + "message": "Make 2-step verification seamless" + }, + "totpHelper": { + "message": "Bitwarden can store and fill 2-step verification codes. Copy and paste the key into this field." + }, + "totpHelperWithCapture": { + "message": "Bitwarden can store and fill 2-step verification codes. Select the camera icon to take a screenshot of this website's authenticator QR code, or copy and paste the key into this field." + }, + "premium": { + "message": "Premium", + "description": "Premium membership" + }, + "cardExpiredTitle": { + "message": "Expired card" + }, + "cardExpiredMessage": { + "message": "If you've renewed it, update the card's information" + }, "verificationRequired": { "message": "Требуется верификация", "description": "Default title for the user verification dialog." @@ -2084,6 +2182,15 @@ "personalOwnershipPolicyInEffectImports": { "message": "Импорт элементов в ваше личное хранилище отключен политикой организации." }, + "personalDetails": { + "message": "Personal details" + }, + "identification": { + "message": "Identification" + }, + "contactInfo": { + "message": "Contact information" + }, "allSends": { "message": "Все Send’ы", "description": "'Sends' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." @@ -2518,6 +2625,9 @@ "generateEmail": { "message": "Сгенерировать email" }, + "usernameGenerator": { + "message": "Username generator" + }, "spinboxBoundariesHint": { "message": "Значение должно быть между $MIN$ и $MAX$.", "description": "Explains spin box minimum and maximum values to the user", @@ -3212,6 +3322,9 @@ "followTheStepsBelowToFinishLoggingIn": { "message": "Следуйте указаниям ниже, чтобы завершить авторизацию." }, + "followTheStepsBelowToFinishLoggingInWithSecurityKey": { + "message": "Выполните следующие шаги, чтобы завершить авторизацию с помощью ключа безопасности." + }, "launchDuo": { "message": "Запустить Duo в браузере" }, @@ -3436,6 +3549,17 @@ "ssoError": { "message": "Не удалось найти свободные порты для авторизации SSO." }, + "securePasswordGenerated": { + "message": "Secure password generated! Don't forget to also update your password on the website." + }, + "useGeneratorHelpTextPartOne": { + "message": "Use the generator", + "description": "This will be used as part of a larger sentence, broken up to include the generator icon. The full sentence will read 'Use the generator [GENERATOR_ICON] to create a strong unique password'" + }, + "useGeneratorHelpTextPartTwo": { + "message": "to create a strong unique password", + "description": "This will be used as part of a larger sentence, broken up to include the generator icon. The full sentence will read 'Use the generator [GENERATOR_ICON] to create a strong unique password'" + }, "biometricsStatusHelptextUnlockNeeded": { "message": "Биометрическая разблокировка недоступна, поскольку сначала требуется разблокировка с помощью PIN-кода или пароля." }, @@ -3514,6 +3638,27 @@ "setupTwoStepLogin": { "message": "Настроить двухэтапную аутентификацию" }, + "itemDetails": { + "message": "Item details" + }, + "itemName": { + "message": "Item name" + }, + "loginCredentials": { + "message": "Login credentials" + }, + "additionalOptions": { + "message": "Additional options" + }, + "itemHistory": { + "message": "Item history" + }, + "lastEdited": { + "message": "Last edited" + }, + "upload": { + "message": "Upload" + }, "newDeviceVerificationNoticeContentPage1": { "message": "Начиная с февраля 2025 года Bitwarden будет отправлять код на электронную почту вашего аккаунта для подтверждения авторизации с новых устройств." }, @@ -3567,5 +3712,35 @@ }, "move": { "message": "Переместить" + }, + "newLoginNudgeTitle": { + "message": "Экономьте время с помощью автозаполнения" + }, + "newLoginNudgeBody": { + "message": "Включите сайт, чтобы этот логин отображался в качестве предложения для автозаполнения." + }, + "newCardNudgeTitle": { + "message": "Оформление заказа через интернет" + }, + "newCardNudgeBody": { + "message": "С помощью карт можно легко и безопасно автоматически заполнять формы оплаты." + }, + "newIdentityNudgeTitle": { + "message": "Упрощение создания аккаунтов" + }, + "newIdentityNudgeBody": { + "message": "С помощью личностей можно быстро заполнять длинные регистрационные или контактные формы." + }, + "newNoteNudgeTitle": { + "message": "Храните ваши конфиденциальные данные в безопасности" + }, + "newNoteNudgeBody": { + "message": "С помощью заметок можно надежно хранить конфиденциальные данные, например, банковские или страховые реквизиты." + }, + "newSshNudgeTitle": { + "message": "Удобный для разработчиков SSH-доступ" + }, + "newSshNudgeBody": { + "message": "Храните свои ключи и подключайтесь с помощью агента SSH для быстрой и зашифрованной аутентификации." } } diff --git a/apps/desktop/src/locales/si/messages.json b/apps/desktop/src/locales/si/messages.json index 6a2e09c0292..d345f7849a0 100644 --- a/apps/desktop/src/locales/si/messages.json +++ b/apps/desktop/src/locales/si/messages.json @@ -393,6 +393,64 @@ "authenticatorKeyTotp": { "message": "Authenticator key (TOTP)" }, + "authenticatorKey": { + "message": "Authenticator key" + }, + "autofillOptions": { + "message": "Autofill options" + }, + "websiteUri": { + "message": "Website (URI)" + }, + "websiteUriCount": { + "message": "Website (URI) $COUNT$", + "description": "Label for an input field that contains a website URI. The input field is part of a list of fields, and the count indicates the position of the field in the list.", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + } + } + }, + "websiteAdded": { + "message": "Website added" + }, + "addWebsite": { + "message": "Add website" + }, + "deleteWebsite": { + "message": "Delete website" + }, + "owner": { + "message": "Owner" + }, + "addField": { + "message": "Add field" + }, + "fieldType": { + "message": "Field type" + }, + "fieldLabel": { + "message": "Field label" + }, + "add": { + "message": "Add" + }, + "textHelpText": { + "message": "Use text fields for data like security questions" + }, + "hiddenHelpText": { + "message": "Use hidden fields for sensitive data like a password" + }, + "checkBoxHelpText": { + "message": "Use checkboxes if you'd like to autofill a form's checkbox, like a remember email" + }, + "linkedHelpText": { + "message": "Use a linked field when you are experiencing autofill issues for a specific website." + }, + "linkedLabelHelpText": { + "message": "Enter the the field's html id, name, aria-label, or placeholder." + }, "folder": { "message": "බහාලුම" }, @@ -418,6 +476,9 @@ "message": "Linked", "description": "This describes a field that is 'linked' (related) to another field." }, + "cfTypeCheckbox": { + "message": "Checkbox" + }, "linkedValue": { "message": "Linked value", "description": "This describes a value that is 'linked' (related) to another value." @@ -1915,6 +1976,43 @@ } } }, + "cardDetails": { + "message": "Card details" + }, + "cardBrandDetails": { + "message": "$BRAND$ details", + "placeholders": { + "brand": { + "content": "$1", + "example": "Visa" + } + } + }, + "learnMoreAboutAuthenticators": { + "message": "Learn more about authenticators" + }, + "copyTOTP": { + "message": "Copy Authenticator key (TOTP)" + }, + "totpHelperTitle": { + "message": "Make 2-step verification seamless" + }, + "totpHelper": { + "message": "Bitwarden can store and fill 2-step verification codes. Copy and paste the key into this field." + }, + "totpHelperWithCapture": { + "message": "Bitwarden can store and fill 2-step verification codes. Select the camera icon to take a screenshot of this website's authenticator QR code, or copy and paste the key into this field." + }, + "premium": { + "message": "Premium", + "description": "Premium membership" + }, + "cardExpiredTitle": { + "message": "Expired card" + }, + "cardExpiredMessage": { + "message": "If you've renewed it, update the card's information" + }, "verificationRequired": { "message": "Verification required", "description": "Default title for the user verification dialog." @@ -2084,6 +2182,15 @@ "personalOwnershipPolicyInEffectImports": { "message": "An organization policy has blocked importing items into your individual vault." }, + "personalDetails": { + "message": "Personal details" + }, + "identification": { + "message": "Identification" + }, + "contactInfo": { + "message": "Contact information" + }, "allSends": { "message": "All Sends", "description": "'Sends' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." @@ -2518,6 +2625,9 @@ "generateEmail": { "message": "Generate email" }, + "usernameGenerator": { + "message": "Username generator" + }, "spinboxBoundariesHint": { "message": "Value must be between $MIN$ and $MAX$.", "description": "Explains spin box minimum and maximum values to the user", @@ -3212,6 +3322,9 @@ "followTheStepsBelowToFinishLoggingIn": { "message": "Follow the steps below to finish logging in." }, + "followTheStepsBelowToFinishLoggingInWithSecurityKey": { + "message": "Follow the steps below to finish logging in with your security key." + }, "launchDuo": { "message": "Launch Duo in Browser" }, @@ -3436,6 +3549,17 @@ "ssoError": { "message": "No free ports could be found for the sso login." }, + "securePasswordGenerated": { + "message": "Secure password generated! Don't forget to also update your password on the website." + }, + "useGeneratorHelpTextPartOne": { + "message": "Use the generator", + "description": "This will be used as part of a larger sentence, broken up to include the generator icon. The full sentence will read 'Use the generator [GENERATOR_ICON] to create a strong unique password'" + }, + "useGeneratorHelpTextPartTwo": { + "message": "to create a strong unique password", + "description": "This will be used as part of a larger sentence, broken up to include the generator icon. The full sentence will read 'Use the generator [GENERATOR_ICON] to create a strong unique password'" + }, "biometricsStatusHelptextUnlockNeeded": { "message": "Biometric unlock is unavailable because PIN or password unlock is required first." }, @@ -3514,6 +3638,27 @@ "setupTwoStepLogin": { "message": "Set up two-step login" }, + "itemDetails": { + "message": "Item details" + }, + "itemName": { + "message": "Item name" + }, + "loginCredentials": { + "message": "Login credentials" + }, + "additionalOptions": { + "message": "Additional options" + }, + "itemHistory": { + "message": "Item history" + }, + "lastEdited": { + "message": "Last edited" + }, + "upload": { + "message": "Upload" + }, "newDeviceVerificationNoticeContentPage1": { "message": "Bitwarden will send a code to your account email to verify logins from new devices starting in February 2025." }, @@ -3567,5 +3712,35 @@ }, "move": { "message": "Move" + }, + "newLoginNudgeTitle": { + "message": "Save time with autofill" + }, + "newLoginNudgeBody": { + "message": "Include a Website so this login appears as an autofill suggestion." + }, + "newCardNudgeTitle": { + "message": "Seamless online checkout" + }, + "newCardNudgeBody": { + "message": "With cards, easily autofill payment forms securely and accurately." + }, + "newIdentityNudgeTitle": { + "message": "Simplify creating accounts" + }, + "newIdentityNudgeBody": { + "message": "With identities, quickly autofill long registration or contact forms." + }, + "newNoteNudgeTitle": { + "message": "Keep your sensitive data safe" + }, + "newNoteNudgeBody": { + "message": "With notes, securely store sensitive data like banking or insurance details." + }, + "newSshNudgeTitle": { + "message": "Developer-friendly SSH access" + }, + "newSshNudgeBody": { + "message": "Store your keys and connect with the SSH agent for fast, encrypted authentication." } } diff --git a/apps/desktop/src/locales/sk/messages.json b/apps/desktop/src/locales/sk/messages.json index 35c53a31296..a65d6c24f5a 100644 --- a/apps/desktop/src/locales/sk/messages.json +++ b/apps/desktop/src/locales/sk/messages.json @@ -393,6 +393,64 @@ "authenticatorKeyTotp": { "message": "Kľúč overovateľa (TOTP)" }, + "authenticatorKey": { + "message": "Overovací kľúč" + }, + "autofillOptions": { + "message": "Možnosti automatického vypĺňania" + }, + "websiteUri": { + "message": "Webová stránka (URI)" + }, + "websiteUriCount": { + "message": "Webová stránka (URI) $COUNT$", + "description": "Label for an input field that contains a website URI. The input field is part of a list of fields, and the count indicates the position of the field in the list.", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + } + } + }, + "websiteAdded": { + "message": "Webová stránka pridaná" + }, + "addWebsite": { + "message": "Pridať webovú stránku" + }, + "deleteWebsite": { + "message": "Odstrániť webovú stránku" + }, + "owner": { + "message": "Vlastník" + }, + "addField": { + "message": "Pridať pole" + }, + "fieldType": { + "message": "Typ poľa" + }, + "fieldLabel": { + "message": "Názov poľa" + }, + "add": { + "message": "Pridať" + }, + "textHelpText": { + "message": "Textové polia používajte pre také údaje, ako sú bezpečnostné otázky" + }, + "hiddenHelpText": { + "message": "Skryté polia požívajte pre citlivé údaje, ako je heslo" + }, + "checkBoxHelpText": { + "message": "Ak chcete automaticky vyplniť začiarkávacie políčko formulára, napríklad zapamätať e-mail, použite začiarkávacie políčka" + }, + "linkedHelpText": { + "message": "Ak máte problémy s automatickým vypĺňaním pre konkrétnu webovú stránku, použite prepojené pole." + }, + "linkedLabelHelpText": { + "message": "Zadajte html atribút poľa id, name, aria-label, alebo placeholder." + }, "folder": { "message": "Priečinok" }, @@ -418,6 +476,9 @@ "message": "Prepojené", "description": "This describes a field that is 'linked' (related) to another field." }, + "cfTypeCheckbox": { + "message": "Začiarkavacie políčko" + }, "linkedValue": { "message": "Prepojená hodnota", "description": "This describes a value that is 'linked' (related) to another value." @@ -1915,6 +1976,43 @@ } } }, + "cardDetails": { + "message": "Podrobnosti o karte" + }, + "cardBrandDetails": { + "message": "Podrobnosti o $BRAND$", + "placeholders": { + "brand": { + "content": "$1", + "example": "Visa" + } + } + }, + "learnMoreAboutAuthenticators": { + "message": "Viac informácií o overovateľoch" + }, + "copyTOTP": { + "message": "Kopírovať kľúč overovateľa (TOTP)" + }, + "totpHelperTitle": { + "message": "Spravte dvojstupňové overenie bezproblémovým" + }, + "totpHelper": { + "message": "Bitwarden umožňuje uložiť a vyplniť kódy dvojstupňového overenia. Skopírujte a vložte kľúč do tohto poľa." + }, + "totpHelperWithCapture": { + "message": "Bitwarden umožňuje uložiť a vyplniť kódy dvojstupňového overenia. Vyberte ikonu fotoaparátu a zosnímajte obrazovku QR kódu overovacej aplikácie tejto webovej stránky alebo skopírujte a vložte kľúč do tohto poľa." + }, + "premium": { + "message": "Prémium", + "description": "Premium membership" + }, + "cardExpiredTitle": { + "message": "Exspirovaná karta" + }, + "cardExpiredMessage": { + "message": "Ak ste kartu obnovili, aktualizujte jej údaje" + }, "verificationRequired": { "message": "Vyžaduje sa overenie", "description": "Default title for the user verification dialog." @@ -2084,6 +2182,15 @@ "personalOwnershipPolicyInEffectImports": { "message": "Zásady organizácie zablokovali importovanie položiek do vášho osobného trezoru." }, + "personalDetails": { + "message": "Osobné údaje" + }, + "identification": { + "message": "Identifikácia" + }, + "contactInfo": { + "message": "Kontaktné informácie" + }, "allSends": { "message": "Všetky Sendy", "description": "'Sends' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." @@ -2518,6 +2625,9 @@ "generateEmail": { "message": "Generovať e-mail" }, + "usernameGenerator": { + "message": "Generátor používateľského mena" + }, "spinboxBoundariesHint": { "message": "Hodnota musí byť medzi $MIN$ a $MAX$.", "description": "Explains spin box minimum and maximum values to the user", @@ -3212,6 +3322,9 @@ "followTheStepsBelowToFinishLoggingIn": { "message": "Na dokončenie prihlásenia postupujte podľa pokynov." }, + "followTheStepsBelowToFinishLoggingInWithSecurityKey": { + "message": "Podľa nasledujúcich krokov dokončite prihlásenie pomocou bezpečnostného kľúča." + }, "launchDuo": { "message": "Spustiť Duo v prehliadači" }, @@ -3436,6 +3549,17 @@ "ssoError": { "message": "Pre prihlásenie SSO sa nepodarilo nájsť žiadne voľné porty." }, + "securePasswordGenerated": { + "message": "Bezpečné heslo vygenerované! Nezabudnite tiež aktualizovať heslo na stránke." + }, + "useGeneratorHelpTextPartOne": { + "message": "Použite generátor", + "description": "This will be used as part of a larger sentence, broken up to include the generator icon. The full sentence will read 'Use the generator [GENERATOR_ICON] to create a strong unique password'" + }, + "useGeneratorHelpTextPartTwo": { + "message": "na vytvorenie silného, unikátneho hesla", + "description": "This will be used as part of a larger sentence, broken up to include the generator icon. The full sentence will read 'Use the generator [GENERATOR_ICON] to create a strong unique password'" + }, "biometricsStatusHelptextUnlockNeeded": { "message": "Odomykanie biometrickými údajmi je nedostupné pretože je najskôr potrebné odomykanie pomocou PIN alebo hesla." }, @@ -3514,6 +3638,27 @@ "setupTwoStepLogin": { "message": "Nastaviť dvojstupňové prihlásenie" }, + "itemDetails": { + "message": "Podrobnosti o položke" + }, + "itemName": { + "message": "Názov položky" + }, + "loginCredentials": { + "message": "Prihlasovacie údaje" + }, + "additionalOptions": { + "message": "Ďalšie možnosti" + }, + "itemHistory": { + "message": "História položky" + }, + "lastEdited": { + "message": "Posledná úprava" + }, + "upload": { + "message": "Nahrať" + }, "newDeviceVerificationNoticeContentPage1": { "message": "Bitwarden vám od februára 2025 pošle na e-mail vášho účtu kód na overenie prihlásenia z nových zariadení." }, @@ -3567,5 +3712,35 @@ }, "move": { "message": "Presunúť" + }, + "newLoginNudgeTitle": { + "message": "Ušetrite čas s automatickým vypĺňaním" + }, + "newLoginNudgeBody": { + "message": "Zadajte webovú stránku, aby sa tieto prihlasovacie údaje zobrazili ako návrh na automatické vyplnenie." + }, + "newCardNudgeTitle": { + "message": "Bezproblémová online registrácia" + }, + "newCardNudgeBody": { + "message": "S kartami môžete jednoducho, bezpečne a presne automaticky vypĺňať platobné formuláre." + }, + "newIdentityNudgeTitle": { + "message": "Zjednodušenie vytvárania účtov" + }, + "newIdentityNudgeBody": { + "message": "Pomocou identít môžete rýchlo automaticky vypĺňať dlhé registračné alebo kontaktné formuláre." + }, + "newNoteNudgeTitle": { + "message": "Udržujte svoje citlivé údaje v bezpečí" + }, + "newNoteNudgeBody": { + "message": "Pomocou poznámok môžete bezpečne ukladať citlivé údaje, napríklad bankové údaje alebo údaje o poistení." + }, + "newSshNudgeTitle": { + "message": "Prístup SSH vhodný pre vývojárov" + }, + "newSshNudgeBody": { + "message": "Uložte si kľúče a pripojte sa pomocou agenta SSH na rýchle šifrované overovanie." } } diff --git a/apps/desktop/src/locales/sl/messages.json b/apps/desktop/src/locales/sl/messages.json index 6fff29c7b4b..fd4c17f949e 100644 --- a/apps/desktop/src/locales/sl/messages.json +++ b/apps/desktop/src/locales/sl/messages.json @@ -393,6 +393,64 @@ "authenticatorKeyTotp": { "message": "Ključ avtentikatorja (TOTP)" }, + "authenticatorKey": { + "message": "Authenticator key" + }, + "autofillOptions": { + "message": "Autofill options" + }, + "websiteUri": { + "message": "Website (URI)" + }, + "websiteUriCount": { + "message": "Website (URI) $COUNT$", + "description": "Label for an input field that contains a website URI. The input field is part of a list of fields, and the count indicates the position of the field in the list.", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + } + } + }, + "websiteAdded": { + "message": "Website added" + }, + "addWebsite": { + "message": "Add website" + }, + "deleteWebsite": { + "message": "Delete website" + }, + "owner": { + "message": "Owner" + }, + "addField": { + "message": "Add field" + }, + "fieldType": { + "message": "Field type" + }, + "fieldLabel": { + "message": "Field label" + }, + "add": { + "message": "Add" + }, + "textHelpText": { + "message": "Use text fields for data like security questions" + }, + "hiddenHelpText": { + "message": "Use hidden fields for sensitive data like a password" + }, + "checkBoxHelpText": { + "message": "Use checkboxes if you'd like to autofill a form's checkbox, like a remember email" + }, + "linkedHelpText": { + "message": "Use a linked field when you are experiencing autofill issues for a specific website." + }, + "linkedLabelHelpText": { + "message": "Enter the the field's html id, name, aria-label, or placeholder." + }, "folder": { "message": "Mapa" }, @@ -418,6 +476,9 @@ "message": "Povezano", "description": "This describes a field that is 'linked' (related) to another field." }, + "cfTypeCheckbox": { + "message": "Checkbox" + }, "linkedValue": { "message": "Povezana vrednost", "description": "This describes a value that is 'linked' (related) to another value." @@ -1915,6 +1976,43 @@ } } }, + "cardDetails": { + "message": "Card details" + }, + "cardBrandDetails": { + "message": "$BRAND$ details", + "placeholders": { + "brand": { + "content": "$1", + "example": "Visa" + } + } + }, + "learnMoreAboutAuthenticators": { + "message": "Learn more about authenticators" + }, + "copyTOTP": { + "message": "Copy Authenticator key (TOTP)" + }, + "totpHelperTitle": { + "message": "Make 2-step verification seamless" + }, + "totpHelper": { + "message": "Bitwarden can store and fill 2-step verification codes. Copy and paste the key into this field." + }, + "totpHelperWithCapture": { + "message": "Bitwarden can store and fill 2-step verification codes. Select the camera icon to take a screenshot of this website's authenticator QR code, or copy and paste the key into this field." + }, + "premium": { + "message": "Premium", + "description": "Premium membership" + }, + "cardExpiredTitle": { + "message": "Expired card" + }, + "cardExpiredMessage": { + "message": "If you've renewed it, update the card's information" + }, "verificationRequired": { "message": "Verification required", "description": "Default title for the user verification dialog." @@ -2084,6 +2182,15 @@ "personalOwnershipPolicyInEffectImports": { "message": "An organization policy has blocked importing items into your individual vault." }, + "personalDetails": { + "message": "Personal details" + }, + "identification": { + "message": "Identification" + }, + "contactInfo": { + "message": "Contact information" + }, "allSends": { "message": "Vsi Sendsi", "description": "'Sends' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." @@ -2518,6 +2625,9 @@ "generateEmail": { "message": "Generate email" }, + "usernameGenerator": { + "message": "Username generator" + }, "spinboxBoundariesHint": { "message": "Value must be between $MIN$ and $MAX$.", "description": "Explains spin box minimum and maximum values to the user", @@ -3212,6 +3322,9 @@ "followTheStepsBelowToFinishLoggingIn": { "message": "Follow the steps below to finish logging in." }, + "followTheStepsBelowToFinishLoggingInWithSecurityKey": { + "message": "Follow the steps below to finish logging in with your security key." + }, "launchDuo": { "message": "Launch Duo in Browser" }, @@ -3436,6 +3549,17 @@ "ssoError": { "message": "No free ports could be found for the sso login." }, + "securePasswordGenerated": { + "message": "Secure password generated! Don't forget to also update your password on the website." + }, + "useGeneratorHelpTextPartOne": { + "message": "Use the generator", + "description": "This will be used as part of a larger sentence, broken up to include the generator icon. The full sentence will read 'Use the generator [GENERATOR_ICON] to create a strong unique password'" + }, + "useGeneratorHelpTextPartTwo": { + "message": "to create a strong unique password", + "description": "This will be used as part of a larger sentence, broken up to include the generator icon. The full sentence will read 'Use the generator [GENERATOR_ICON] to create a strong unique password'" + }, "biometricsStatusHelptextUnlockNeeded": { "message": "Biometric unlock is unavailable because PIN or password unlock is required first." }, @@ -3514,6 +3638,27 @@ "setupTwoStepLogin": { "message": "Set up two-step login" }, + "itemDetails": { + "message": "Item details" + }, + "itemName": { + "message": "Item name" + }, + "loginCredentials": { + "message": "Login credentials" + }, + "additionalOptions": { + "message": "Additional options" + }, + "itemHistory": { + "message": "Item history" + }, + "lastEdited": { + "message": "Last edited" + }, + "upload": { + "message": "Upload" + }, "newDeviceVerificationNoticeContentPage1": { "message": "Bitwarden will send a code to your account email to verify logins from new devices starting in February 2025." }, @@ -3567,5 +3712,35 @@ }, "move": { "message": "Move" + }, + "newLoginNudgeTitle": { + "message": "Save time with autofill" + }, + "newLoginNudgeBody": { + "message": "Include a Website so this login appears as an autofill suggestion." + }, + "newCardNudgeTitle": { + "message": "Seamless online checkout" + }, + "newCardNudgeBody": { + "message": "With cards, easily autofill payment forms securely and accurately." + }, + "newIdentityNudgeTitle": { + "message": "Simplify creating accounts" + }, + "newIdentityNudgeBody": { + "message": "With identities, quickly autofill long registration or contact forms." + }, + "newNoteNudgeTitle": { + "message": "Keep your sensitive data safe" + }, + "newNoteNudgeBody": { + "message": "With notes, securely store sensitive data like banking or insurance details." + }, + "newSshNudgeTitle": { + "message": "Developer-friendly SSH access" + }, + "newSshNudgeBody": { + "message": "Store your keys and connect with the SSH agent for fast, encrypted authentication." } } diff --git a/apps/desktop/src/locales/sr/messages.json b/apps/desktop/src/locales/sr/messages.json index 20f63b32703..aa0ac47cd9d 100644 --- a/apps/desktop/src/locales/sr/messages.json +++ b/apps/desktop/src/locales/sr/messages.json @@ -393,6 +393,64 @@ "authenticatorKeyTotp": { "message": "Једнократни код" }, + "authenticatorKey": { + "message": "Кључ аутентификатора" + }, + "autofillOptions": { + "message": "Опције Ауто-пуњења" + }, + "websiteUri": { + "message": "Вебсајт (URI)" + }, + "websiteUriCount": { + "message": "Сајт (URI) $COUNT$", + "description": "Label for an input field that contains a website URI. The input field is part of a list of fields, and the count indicates the position of the field in the list.", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + } + } + }, + "websiteAdded": { + "message": "Вебсајт додат" + }, + "addWebsite": { + "message": "Додај вебсајт" + }, + "deleteWebsite": { + "message": "Обриши вебсајт" + }, + "owner": { + "message": "Власник" + }, + "addField": { + "message": "Додај поље" + }, + "fieldType": { + "message": "Врста поља" + }, + "fieldLabel": { + "message": "Ознака поља" + }, + "add": { + "message": "Додај" + }, + "textHelpText": { + "message": "Користите текстуална поља за податке као што су безбедносна питања" + }, + "hiddenHelpText": { + "message": "Користите скривена поља за осетљиве податке као што је лозинка" + }, + "checkBoxHelpText": { + "message": "Користите поља за потврду ако желите да аутоматски попуните поље за потврду обрасца, на пример имејл за памћење" + }, + "linkedHelpText": { + "message": "Користите повезано поље када имате проблема са аутоматским попуњавањем за одређену веб локацију." + }, + "linkedLabelHelpText": { + "message": "Унесите html Ид поља, име, aria-label, или placeholder." + }, "folder": { "message": "Фасцикла" }, @@ -418,6 +476,9 @@ "message": "Повезано", "description": "This describes a field that is 'linked' (related) to another field." }, + "cfTypeCheckbox": { + "message": "Поље за потврду" + }, "linkedValue": { "message": "Вредност повезана", "description": "This describes a value that is 'linked' (related) to another value." @@ -1915,6 +1976,43 @@ } } }, + "cardDetails": { + "message": "Детаљи картице" + }, + "cardBrandDetails": { + "message": "$BRAND$ детаљи", + "placeholders": { + "brand": { + "content": "$1", + "example": "Visa" + } + } + }, + "learnMoreAboutAuthenticators": { + "message": "Сазнајте више о аутентификаторима" + }, + "copyTOTP": { + "message": "Копирати једнократни кôд (TOTP)" + }, + "totpHelperTitle": { + "message": "Учините верификацију у 2 корака беспрекорном" + }, + "totpHelper": { + "message": "Bitwarden може да чува и попуњава верификационе кодове у 2 корака. Копирајте и налепите кључ у ово поље." + }, + "totpHelperWithCapture": { + "message": "Bitwarden може да чува и попуњава верификационе кодове у 2 корака. Изаберите икону камере да бисте направили снимак екрана QR кода за аутентификацију ове веб локације или копирајте и налепите кључ у ово поље." + }, + "premium": { + "message": "Премијум", + "description": "Premium membership" + }, + "cardExpiredTitle": { + "message": "Картица је истекла" + }, + "cardExpiredMessage": { + "message": "Ако сте је обновили, ажурирајте податке о картици" + }, "verificationRequired": { "message": "Потребдна верификација", "description": "Default title for the user verification dialog." @@ -2073,7 +2171,7 @@ "message": "Због смерница за предузећа, ограничено вам је чување предмета у вашем личном трезору. Промените опцију власништва у организацију и изаберите из доступних колекција." }, "yourNewPasswordCannotBeTheSameAsYourCurrentPassword": { - "message": "Your new password cannot be the same as your current password." + "message": "Ваша нова лозинка не може бити иста као тренутна лозинка." }, "hintEqualsPassword": { "message": "Ваш савет за лозинку не може да буде исти као лозинка." @@ -2084,6 +2182,15 @@ "personalOwnershipPolicyInEffectImports": { "message": "Политика организације је блокирала увоз ставки у ваш појединачни сеф." }, + "personalDetails": { + "message": "Личне информације" + }, + "identification": { + "message": "Идентификација" + }, + "contactInfo": { + "message": "Контакт подаци" + }, "allSends": { "message": "Сва слања", "description": "'Sends' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." @@ -2518,6 +2625,9 @@ "generateEmail": { "message": "Генеришите имејл" }, + "usernameGenerator": { + "message": "Генератор корисничког имена" + }, "spinboxBoundariesHint": { "message": "Вредност мора бити између $MIN$ и $MAX$.", "description": "Explains spin box minimum and maximum values to the user", @@ -3212,6 +3322,9 @@ "followTheStepsBelowToFinishLoggingIn": { "message": "Следите наведене кораке да бисте завршили пријављивање." }, + "followTheStepsBelowToFinishLoggingInWithSecurityKey": { + "message": "Следите наведене кораке да бисте завршили пријаву са својим безбедносним кључем." + }, "launchDuo": { "message": "Покренути Duo у претраживачу" }, @@ -3436,6 +3549,17 @@ "ssoError": { "message": "Нису пронађени портови за SSO пријаву." }, + "securePasswordGenerated": { + "message": "Сигурна лозинка је генерисана! Не заборавите да ажурирате и своју лозинку на веб локацији." + }, + "useGeneratorHelpTextPartOne": { + "message": "Употребити генератор", + "description": "This will be used as part of a larger sentence, broken up to include the generator icon. The full sentence will read 'Use the generator [GENERATOR_ICON] to create a strong unique password'" + }, + "useGeneratorHelpTextPartTwo": { + "message": "да креирате јаку јединствену лозинку", + "description": "This will be used as part of a larger sentence, broken up to include the generator icon. The full sentence will read 'Use the generator [GENERATOR_ICON] to create a strong unique password'" + }, "biometricsStatusHelptextUnlockNeeded": { "message": "Биометријско откључавање није доступно јер је пре тога потребно унети ПИН или лозинку за откључавање." }, @@ -3514,6 +3638,27 @@ "setupTwoStepLogin": { "message": "Поставити дво-степенску пријаву" }, + "itemDetails": { + "message": "Детаљи ставке" + }, + "itemName": { + "message": "Име ставке" + }, + "loginCredentials": { + "message": "Акредитиве за пријављивање" + }, + "additionalOptions": { + "message": "Додатне опције" + }, + "itemHistory": { + "message": "Историја предмета" + }, + "lastEdited": { + "message": "Последња измена" + }, + "upload": { + "message": "Отпреми" + }, "newDeviceVerificationNoticeContentPage1": { "message": "Bitwarden ће послати кôд на имејл вашег налога за верификовање пријављивања са нових уређаја почевши од фебруара 2025." }, @@ -3566,6 +3711,36 @@ "message": "Променити ризичну лозинку" }, "move": { - "message": "Move" + "message": "Премести" + }, + "newLoginNudgeTitle": { + "message": "Save time with autofill" + }, + "newLoginNudgeBody": { + "message": "Include a Website so this login appears as an autofill suggestion." + }, + "newCardNudgeTitle": { + "message": "Seamless online checkout" + }, + "newCardNudgeBody": { + "message": "With cards, easily autofill payment forms securely and accurately." + }, + "newIdentityNudgeTitle": { + "message": "Simplify creating accounts" + }, + "newIdentityNudgeBody": { + "message": "With identities, quickly autofill long registration or contact forms." + }, + "newNoteNudgeTitle": { + "message": "Keep your sensitive data safe" + }, + "newNoteNudgeBody": { + "message": "With notes, securely store sensitive data like banking or insurance details." + }, + "newSshNudgeTitle": { + "message": "Developer-friendly SSH access" + }, + "newSshNudgeBody": { + "message": "Store your keys and connect with the SSH agent for fast, encrypted authentication." } } diff --git a/apps/desktop/src/locales/sv/messages.json b/apps/desktop/src/locales/sv/messages.json index 2f051323f9f..5ca6c5b9372 100644 --- a/apps/desktop/src/locales/sv/messages.json +++ b/apps/desktop/src/locales/sv/messages.json @@ -393,6 +393,64 @@ "authenticatorKeyTotp": { "message": "Autentiseringsnyckel (TOTP)" }, + "authenticatorKey": { + "message": "Authenticator key" + }, + "autofillOptions": { + "message": "Autofill options" + }, + "websiteUri": { + "message": "Website (URI)" + }, + "websiteUriCount": { + "message": "Website (URI) $COUNT$", + "description": "Label for an input field that contains a website URI. The input field is part of a list of fields, and the count indicates the position of the field in the list.", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + } + } + }, + "websiteAdded": { + "message": "Website added" + }, + "addWebsite": { + "message": "Add website" + }, + "deleteWebsite": { + "message": "Delete website" + }, + "owner": { + "message": "Owner" + }, + "addField": { + "message": "Add field" + }, + "fieldType": { + "message": "Field type" + }, + "fieldLabel": { + "message": "Field label" + }, + "add": { + "message": "Add" + }, + "textHelpText": { + "message": "Use text fields for data like security questions" + }, + "hiddenHelpText": { + "message": "Use hidden fields for sensitive data like a password" + }, + "checkBoxHelpText": { + "message": "Use checkboxes if you'd like to autofill a form's checkbox, like a remember email" + }, + "linkedHelpText": { + "message": "Use a linked field when you are experiencing autofill issues for a specific website." + }, + "linkedLabelHelpText": { + "message": "Enter the the field's html id, name, aria-label, or placeholder." + }, "folder": { "message": "Mapp" }, @@ -418,6 +476,9 @@ "message": "Länkad", "description": "This describes a field that is 'linked' (related) to another field." }, + "cfTypeCheckbox": { + "message": "Checkbox" + }, "linkedValue": { "message": "Länkat värde", "description": "This describes a value that is 'linked' (related) to another value." @@ -1915,6 +1976,43 @@ } } }, + "cardDetails": { + "message": "Card details" + }, + "cardBrandDetails": { + "message": "$BRAND$ details", + "placeholders": { + "brand": { + "content": "$1", + "example": "Visa" + } + } + }, + "learnMoreAboutAuthenticators": { + "message": "Learn more about authenticators" + }, + "copyTOTP": { + "message": "Copy Authenticator key (TOTP)" + }, + "totpHelperTitle": { + "message": "Make 2-step verification seamless" + }, + "totpHelper": { + "message": "Bitwarden can store and fill 2-step verification codes. Copy and paste the key into this field." + }, + "totpHelperWithCapture": { + "message": "Bitwarden can store and fill 2-step verification codes. Select the camera icon to take a screenshot of this website's authenticator QR code, or copy and paste the key into this field." + }, + "premium": { + "message": "Premium", + "description": "Premium membership" + }, + "cardExpiredTitle": { + "message": "Expired card" + }, + "cardExpiredMessage": { + "message": "If you've renewed it, update the card's information" + }, "verificationRequired": { "message": "Verifiering krävs", "description": "Default title for the user verification dialog." @@ -2084,6 +2182,15 @@ "personalOwnershipPolicyInEffectImports": { "message": "En organisationspolicy har blockerat importering av objekt till ditt personliga valv." }, + "personalDetails": { + "message": "Personal details" + }, + "identification": { + "message": "Identification" + }, + "contactInfo": { + "message": "Contact information" + }, "allSends": { "message": "Alla Sends", "description": "'Sends' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." @@ -2518,6 +2625,9 @@ "generateEmail": { "message": "Generate email" }, + "usernameGenerator": { + "message": "Username generator" + }, "spinboxBoundariesHint": { "message": "Value must be between $MIN$ and $MAX$.", "description": "Explains spin box minimum and maximum values to the user", @@ -3212,6 +3322,9 @@ "followTheStepsBelowToFinishLoggingIn": { "message": "Follow the steps below to finish logging in." }, + "followTheStepsBelowToFinishLoggingInWithSecurityKey": { + "message": "Follow the steps below to finish logging in with your security key." + }, "launchDuo": { "message": "Starta Duo i webbläsaren" }, @@ -3436,6 +3549,17 @@ "ssoError": { "message": "No free ports could be found for the sso login." }, + "securePasswordGenerated": { + "message": "Secure password generated! Don't forget to also update your password on the website." + }, + "useGeneratorHelpTextPartOne": { + "message": "Use the generator", + "description": "This will be used as part of a larger sentence, broken up to include the generator icon. The full sentence will read 'Use the generator [GENERATOR_ICON] to create a strong unique password'" + }, + "useGeneratorHelpTextPartTwo": { + "message": "to create a strong unique password", + "description": "This will be used as part of a larger sentence, broken up to include the generator icon. The full sentence will read 'Use the generator [GENERATOR_ICON] to create a strong unique password'" + }, "biometricsStatusHelptextUnlockNeeded": { "message": "Biometric unlock is unavailable because PIN or password unlock is required first." }, @@ -3514,6 +3638,27 @@ "setupTwoStepLogin": { "message": "Set up two-step login" }, + "itemDetails": { + "message": "Item details" + }, + "itemName": { + "message": "Item name" + }, + "loginCredentials": { + "message": "Login credentials" + }, + "additionalOptions": { + "message": "Additional options" + }, + "itemHistory": { + "message": "Item history" + }, + "lastEdited": { + "message": "Last edited" + }, + "upload": { + "message": "Upload" + }, "newDeviceVerificationNoticeContentPage1": { "message": "Bitwarden will send a code to your account email to verify logins from new devices starting in February 2025." }, @@ -3567,5 +3712,35 @@ }, "move": { "message": "Move" + }, + "newLoginNudgeTitle": { + "message": "Save time with autofill" + }, + "newLoginNudgeBody": { + "message": "Include a Website so this login appears as an autofill suggestion." + }, + "newCardNudgeTitle": { + "message": "Seamless online checkout" + }, + "newCardNudgeBody": { + "message": "With cards, easily autofill payment forms securely and accurately." + }, + "newIdentityNudgeTitle": { + "message": "Simplify creating accounts" + }, + "newIdentityNudgeBody": { + "message": "With identities, quickly autofill long registration or contact forms." + }, + "newNoteNudgeTitle": { + "message": "Keep your sensitive data safe" + }, + "newNoteNudgeBody": { + "message": "With notes, securely store sensitive data like banking or insurance details." + }, + "newSshNudgeTitle": { + "message": "Developer-friendly SSH access" + }, + "newSshNudgeBody": { + "message": "Store your keys and connect with the SSH agent for fast, encrypted authentication." } } diff --git a/apps/desktop/src/locales/te/messages.json b/apps/desktop/src/locales/te/messages.json index 8850cbe5a3f..2350e0df4c7 100644 --- a/apps/desktop/src/locales/te/messages.json +++ b/apps/desktop/src/locales/te/messages.json @@ -393,6 +393,64 @@ "authenticatorKeyTotp": { "message": "Authenticator key (TOTP)" }, + "authenticatorKey": { + "message": "Authenticator key" + }, + "autofillOptions": { + "message": "Autofill options" + }, + "websiteUri": { + "message": "Website (URI)" + }, + "websiteUriCount": { + "message": "Website (URI) $COUNT$", + "description": "Label for an input field that contains a website URI. The input field is part of a list of fields, and the count indicates the position of the field in the list.", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + } + } + }, + "websiteAdded": { + "message": "Website added" + }, + "addWebsite": { + "message": "Add website" + }, + "deleteWebsite": { + "message": "Delete website" + }, + "owner": { + "message": "Owner" + }, + "addField": { + "message": "Add field" + }, + "fieldType": { + "message": "Field type" + }, + "fieldLabel": { + "message": "Field label" + }, + "add": { + "message": "Add" + }, + "textHelpText": { + "message": "Use text fields for data like security questions" + }, + "hiddenHelpText": { + "message": "Use hidden fields for sensitive data like a password" + }, + "checkBoxHelpText": { + "message": "Use checkboxes if you'd like to autofill a form's checkbox, like a remember email" + }, + "linkedHelpText": { + "message": "Use a linked field when you are experiencing autofill issues for a specific website." + }, + "linkedLabelHelpText": { + "message": "Enter the the field's html id, name, aria-label, or placeholder." + }, "folder": { "message": "Folder" }, @@ -418,6 +476,9 @@ "message": "Linked", "description": "This describes a field that is 'linked' (related) to another field." }, + "cfTypeCheckbox": { + "message": "Checkbox" + }, "linkedValue": { "message": "Linked value", "description": "This describes a value that is 'linked' (related) to another value." @@ -1915,6 +1976,43 @@ } } }, + "cardDetails": { + "message": "Card details" + }, + "cardBrandDetails": { + "message": "$BRAND$ details", + "placeholders": { + "brand": { + "content": "$1", + "example": "Visa" + } + } + }, + "learnMoreAboutAuthenticators": { + "message": "Learn more about authenticators" + }, + "copyTOTP": { + "message": "Copy Authenticator key (TOTP)" + }, + "totpHelperTitle": { + "message": "Make 2-step verification seamless" + }, + "totpHelper": { + "message": "Bitwarden can store and fill 2-step verification codes. Copy and paste the key into this field." + }, + "totpHelperWithCapture": { + "message": "Bitwarden can store and fill 2-step verification codes. Select the camera icon to take a screenshot of this website's authenticator QR code, or copy and paste the key into this field." + }, + "premium": { + "message": "Premium", + "description": "Premium membership" + }, + "cardExpiredTitle": { + "message": "Expired card" + }, + "cardExpiredMessage": { + "message": "If you've renewed it, update the card's information" + }, "verificationRequired": { "message": "Verification required", "description": "Default title for the user verification dialog." @@ -2084,6 +2182,15 @@ "personalOwnershipPolicyInEffectImports": { "message": "An organization policy has blocked importing items into your individual vault." }, + "personalDetails": { + "message": "Personal details" + }, + "identification": { + "message": "Identification" + }, + "contactInfo": { + "message": "Contact information" + }, "allSends": { "message": "All Sends", "description": "'Sends' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." @@ -2518,6 +2625,9 @@ "generateEmail": { "message": "Generate email" }, + "usernameGenerator": { + "message": "Username generator" + }, "spinboxBoundariesHint": { "message": "Value must be between $MIN$ and $MAX$.", "description": "Explains spin box minimum and maximum values to the user", @@ -3212,6 +3322,9 @@ "followTheStepsBelowToFinishLoggingIn": { "message": "Follow the steps below to finish logging in." }, + "followTheStepsBelowToFinishLoggingInWithSecurityKey": { + "message": "Follow the steps below to finish logging in with your security key." + }, "launchDuo": { "message": "Launch Duo in Browser" }, @@ -3436,6 +3549,17 @@ "ssoError": { "message": "No free ports could be found for the sso login." }, + "securePasswordGenerated": { + "message": "Secure password generated! Don't forget to also update your password on the website." + }, + "useGeneratorHelpTextPartOne": { + "message": "Use the generator", + "description": "This will be used as part of a larger sentence, broken up to include the generator icon. The full sentence will read 'Use the generator [GENERATOR_ICON] to create a strong unique password'" + }, + "useGeneratorHelpTextPartTwo": { + "message": "to create a strong unique password", + "description": "This will be used as part of a larger sentence, broken up to include the generator icon. The full sentence will read 'Use the generator [GENERATOR_ICON] to create a strong unique password'" + }, "biometricsStatusHelptextUnlockNeeded": { "message": "Biometric unlock is unavailable because PIN or password unlock is required first." }, @@ -3514,6 +3638,27 @@ "setupTwoStepLogin": { "message": "Set up two-step login" }, + "itemDetails": { + "message": "Item details" + }, + "itemName": { + "message": "Item name" + }, + "loginCredentials": { + "message": "Login credentials" + }, + "additionalOptions": { + "message": "Additional options" + }, + "itemHistory": { + "message": "Item history" + }, + "lastEdited": { + "message": "Last edited" + }, + "upload": { + "message": "Upload" + }, "newDeviceVerificationNoticeContentPage1": { "message": "Bitwarden will send a code to your account email to verify logins from new devices starting in February 2025." }, @@ -3567,5 +3712,35 @@ }, "move": { "message": "Move" + }, + "newLoginNudgeTitle": { + "message": "Save time with autofill" + }, + "newLoginNudgeBody": { + "message": "Include a Website so this login appears as an autofill suggestion." + }, + "newCardNudgeTitle": { + "message": "Seamless online checkout" + }, + "newCardNudgeBody": { + "message": "With cards, easily autofill payment forms securely and accurately." + }, + "newIdentityNudgeTitle": { + "message": "Simplify creating accounts" + }, + "newIdentityNudgeBody": { + "message": "With identities, quickly autofill long registration or contact forms." + }, + "newNoteNudgeTitle": { + "message": "Keep your sensitive data safe" + }, + "newNoteNudgeBody": { + "message": "With notes, securely store sensitive data like banking or insurance details." + }, + "newSshNudgeTitle": { + "message": "Developer-friendly SSH access" + }, + "newSshNudgeBody": { + "message": "Store your keys and connect with the SSH agent for fast, encrypted authentication." } } diff --git a/apps/desktop/src/locales/th/messages.json b/apps/desktop/src/locales/th/messages.json index 0edc3fe527a..6bd75b86398 100644 --- a/apps/desktop/src/locales/th/messages.json +++ b/apps/desktop/src/locales/th/messages.json @@ -393,6 +393,64 @@ "authenticatorKeyTotp": { "message": "คีย์ Authenticator (TOTP)" }, + "authenticatorKey": { + "message": "Authenticator key" + }, + "autofillOptions": { + "message": "Autofill options" + }, + "websiteUri": { + "message": "Website (URI)" + }, + "websiteUriCount": { + "message": "Website (URI) $COUNT$", + "description": "Label for an input field that contains a website URI. The input field is part of a list of fields, and the count indicates the position of the field in the list.", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + } + } + }, + "websiteAdded": { + "message": "Website added" + }, + "addWebsite": { + "message": "Add website" + }, + "deleteWebsite": { + "message": "Delete website" + }, + "owner": { + "message": "Owner" + }, + "addField": { + "message": "Add field" + }, + "fieldType": { + "message": "Field type" + }, + "fieldLabel": { + "message": "Field label" + }, + "add": { + "message": "Add" + }, + "textHelpText": { + "message": "Use text fields for data like security questions" + }, + "hiddenHelpText": { + "message": "Use hidden fields for sensitive data like a password" + }, + "checkBoxHelpText": { + "message": "Use checkboxes if you'd like to autofill a form's checkbox, like a remember email" + }, + "linkedHelpText": { + "message": "Use a linked field when you are experiencing autofill issues for a specific website." + }, + "linkedLabelHelpText": { + "message": "Enter the the field's html id, name, aria-label, or placeholder." + }, "folder": { "message": "โฟลเดอร์" }, @@ -418,6 +476,9 @@ "message": "เชื่อมโยงแล้ว", "description": "This describes a field that is 'linked' (related) to another field." }, + "cfTypeCheckbox": { + "message": "Checkbox" + }, "linkedValue": { "message": "เชื่อมโยงค่าแล้ว", "description": "This describes a value that is 'linked' (related) to another value." @@ -1915,6 +1976,43 @@ } } }, + "cardDetails": { + "message": "Card details" + }, + "cardBrandDetails": { + "message": "$BRAND$ details", + "placeholders": { + "brand": { + "content": "$1", + "example": "Visa" + } + } + }, + "learnMoreAboutAuthenticators": { + "message": "Learn more about authenticators" + }, + "copyTOTP": { + "message": "Copy Authenticator key (TOTP)" + }, + "totpHelperTitle": { + "message": "Make 2-step verification seamless" + }, + "totpHelper": { + "message": "Bitwarden can store and fill 2-step verification codes. Copy and paste the key into this field." + }, + "totpHelperWithCapture": { + "message": "Bitwarden can store and fill 2-step verification codes. Select the camera icon to take a screenshot of this website's authenticator QR code, or copy and paste the key into this field." + }, + "premium": { + "message": "Premium", + "description": "Premium membership" + }, + "cardExpiredTitle": { + "message": "Expired card" + }, + "cardExpiredMessage": { + "message": "If you've renewed it, update the card's information" + }, "verificationRequired": { "message": "Verification required", "description": "Default title for the user verification dialog." @@ -2084,6 +2182,15 @@ "personalOwnershipPolicyInEffectImports": { "message": "An organization policy has blocked importing items into your individual vault." }, + "personalDetails": { + "message": "Personal details" + }, + "identification": { + "message": "Identification" + }, + "contactInfo": { + "message": "Contact information" + }, "allSends": { "message": "ส่งทั้งหมด", "description": "'Sends' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." @@ -2518,6 +2625,9 @@ "generateEmail": { "message": "Generate email" }, + "usernameGenerator": { + "message": "Username generator" + }, "spinboxBoundariesHint": { "message": "Value must be between $MIN$ and $MAX$.", "description": "Explains spin box minimum and maximum values to the user", @@ -3212,6 +3322,9 @@ "followTheStepsBelowToFinishLoggingIn": { "message": "Follow the steps below to finish logging in." }, + "followTheStepsBelowToFinishLoggingInWithSecurityKey": { + "message": "Follow the steps below to finish logging in with your security key." + }, "launchDuo": { "message": "Launch Duo in Browser" }, @@ -3436,6 +3549,17 @@ "ssoError": { "message": "No free ports could be found for the sso login." }, + "securePasswordGenerated": { + "message": "Secure password generated! Don't forget to also update your password on the website." + }, + "useGeneratorHelpTextPartOne": { + "message": "Use the generator", + "description": "This will be used as part of a larger sentence, broken up to include the generator icon. The full sentence will read 'Use the generator [GENERATOR_ICON] to create a strong unique password'" + }, + "useGeneratorHelpTextPartTwo": { + "message": "to create a strong unique password", + "description": "This will be used as part of a larger sentence, broken up to include the generator icon. The full sentence will read 'Use the generator [GENERATOR_ICON] to create a strong unique password'" + }, "biometricsStatusHelptextUnlockNeeded": { "message": "Biometric unlock is unavailable because PIN or password unlock is required first." }, @@ -3514,6 +3638,27 @@ "setupTwoStepLogin": { "message": "Set up two-step login" }, + "itemDetails": { + "message": "Item details" + }, + "itemName": { + "message": "Item name" + }, + "loginCredentials": { + "message": "Login credentials" + }, + "additionalOptions": { + "message": "Additional options" + }, + "itemHistory": { + "message": "Item history" + }, + "lastEdited": { + "message": "Last edited" + }, + "upload": { + "message": "Upload" + }, "newDeviceVerificationNoticeContentPage1": { "message": "Bitwarden will send a code to your account email to verify logins from new devices starting in February 2025." }, @@ -3567,5 +3712,35 @@ }, "move": { "message": "Move" + }, + "newLoginNudgeTitle": { + "message": "Save time with autofill" + }, + "newLoginNudgeBody": { + "message": "Include a Website so this login appears as an autofill suggestion." + }, + "newCardNudgeTitle": { + "message": "Seamless online checkout" + }, + "newCardNudgeBody": { + "message": "With cards, easily autofill payment forms securely and accurately." + }, + "newIdentityNudgeTitle": { + "message": "Simplify creating accounts" + }, + "newIdentityNudgeBody": { + "message": "With identities, quickly autofill long registration or contact forms." + }, + "newNoteNudgeTitle": { + "message": "Keep your sensitive data safe" + }, + "newNoteNudgeBody": { + "message": "With notes, securely store sensitive data like banking or insurance details." + }, + "newSshNudgeTitle": { + "message": "Developer-friendly SSH access" + }, + "newSshNudgeBody": { + "message": "Store your keys and connect with the SSH agent for fast, encrypted authentication." } } diff --git a/apps/desktop/src/locales/tr/messages.json b/apps/desktop/src/locales/tr/messages.json index 76dc32239be..3100cbabcbb 100644 --- a/apps/desktop/src/locales/tr/messages.json +++ b/apps/desktop/src/locales/tr/messages.json @@ -393,6 +393,64 @@ "authenticatorKeyTotp": { "message": "Kimlik doğrulama anahtarı (TOTP)" }, + "authenticatorKey": { + "message": "Kimlik doğrulama anahtarı" + }, + "autofillOptions": { + "message": "Otomatik doldurma seçenekleri" + }, + "websiteUri": { + "message": "Web sitesi (URI)" + }, + "websiteUriCount": { + "message": "Web sitesi (URI) $COUNT$", + "description": "Label for an input field that contains a website URI. The input field is part of a list of fields, and the count indicates the position of the field in the list.", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + } + } + }, + "websiteAdded": { + "message": "Web sitesi eklendi" + }, + "addWebsite": { + "message": "Web sitesi ekle" + }, + "deleteWebsite": { + "message": "Web sitesini sil" + }, + "owner": { + "message": "Sahibi" + }, + "addField": { + "message": "Alan ekle" + }, + "fieldType": { + "message": "Alan türü" + }, + "fieldLabel": { + "message": "Alan etiketi" + }, + "add": { + "message": "Ekle" + }, + "textHelpText": { + "message": "Güvenlik sorusu gibi veriler için metin alanlarını kullanın" + }, + "hiddenHelpText": { + "message": "Parola gibi hassas verileri için gizli alanları kullanın" + }, + "checkBoxHelpText": { + "message": "Formlardaki onay kutularını (örn. \"e-posta adresimi hatırla\") otomatik doldurmak isterseniz onay kutularını kullanın" + }, + "linkedHelpText": { + "message": "Use a linked field when you are experiencing autofill issues for a specific website." + }, + "linkedLabelHelpText": { + "message": "Enter the the field's html id, name, aria-label, or placeholder." + }, "folder": { "message": "Klasör" }, @@ -418,6 +476,9 @@ "message": "Bağlantılı", "description": "This describes a field that is 'linked' (related) to another field." }, + "cfTypeCheckbox": { + "message": "Onay kutusu" + }, "linkedValue": { "message": "Bağlı değer", "description": "This describes a value that is 'linked' (related) to another value." @@ -1915,6 +1976,43 @@ } } }, + "cardDetails": { + "message": "Kart bilgileri" + }, + "cardBrandDetails": { + "message": "$BRAND$ bilgileri", + "placeholders": { + "brand": { + "content": "$1", + "example": "Visa" + } + } + }, + "learnMoreAboutAuthenticators": { + "message": "Kimlik doğrulayıcılar hakkında bilgi alın" + }, + "copyTOTP": { + "message": "Kimlik doğrulama anahtarını kopyala (TOTP)" + }, + "totpHelperTitle": { + "message": "Make 2-step verification seamless" + }, + "totpHelper": { + "message": "Bitwarden can store and fill 2-step verification codes. Copy and paste the key into this field." + }, + "totpHelperWithCapture": { + "message": "Bitwarden can store and fill 2-step verification codes. Select the camera icon to take a screenshot of this website's authenticator QR code, or copy and paste the key into this field." + }, + "premium": { + "message": "Premium", + "description": "Premium membership" + }, + "cardExpiredTitle": { + "message": "Kartın süresi dolmuş" + }, + "cardExpiredMessage": { + "message": "Kartı yenilediyseniz kart bilgilerini güncelleyin" + }, "verificationRequired": { "message": "Doğrulama gerekli", "description": "Default title for the user verification dialog." @@ -2084,6 +2182,15 @@ "personalOwnershipPolicyInEffectImports": { "message": "Bir kuruluş ilkesi, kayıtları kişisel kasanıza içe aktarmayı engelledi." }, + "personalDetails": { + "message": "Kişisel bilgiler" + }, + "identification": { + "message": "Kimlik" + }, + "contactInfo": { + "message": "İletişim bilgileri" + }, "allSends": { "message": "Tüm Send'ler", "description": "'Sends' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." @@ -2518,6 +2625,9 @@ "generateEmail": { "message": "E-posta oluştur" }, + "usernameGenerator": { + "message": "Kullanıcı adı üreteci" + }, "spinboxBoundariesHint": { "message": "Değer $MIN$ ile $MAX$ arasında olmalıdır.", "description": "Explains spin box minimum and maximum values to the user", @@ -3212,6 +3322,9 @@ "followTheStepsBelowToFinishLoggingIn": { "message": "Follow the steps below to finish logging in." }, + "followTheStepsBelowToFinishLoggingInWithSecurityKey": { + "message": "Güvenlik anahtarınızla girişi tamamlamak için aşağıdaki adımları izleyin." + }, "launchDuo": { "message": "Duo'yu tarayıcıda aç" }, @@ -3436,6 +3549,17 @@ "ssoError": { "message": "SSO girişi için açık port bulunamadı." }, + "securePasswordGenerated": { + "message": "Güvenli parola üretildi. Web sitesindeki parolanızı da güncellemeyi unutmayın." + }, + "useGeneratorHelpTextPartOne": { + "message": "Üreteci kullanarak", + "description": "This will be used as part of a larger sentence, broken up to include the generator icon. The full sentence will read 'Use the generator [GENERATOR_ICON] to create a strong unique password'" + }, + "useGeneratorHelpTextPartTwo": { + "message": "güçlü ve benzersiz parola üretebilirsiniz", + "description": "This will be used as part of a larger sentence, broken up to include the generator icon. The full sentence will read 'Use the generator [GENERATOR_ICON] to create a strong unique password'" + }, "biometricsStatusHelptextUnlockNeeded": { "message": "Biometric unlock is unavailable because PIN or password unlock is required first." }, @@ -3514,6 +3638,27 @@ "setupTwoStepLogin": { "message": "İki adımlı girişi ayarlayın" }, + "itemDetails": { + "message": "Kayıt ayrıntıları" + }, + "itemName": { + "message": "Kayıt adı" + }, + "loginCredentials": { + "message": "Hesap bilgileri" + }, + "additionalOptions": { + "message": "Ek seçenekler" + }, + "itemHistory": { + "message": "Kayıt geçmişi" + }, + "lastEdited": { + "message": "Son düzenlenme" + }, + "upload": { + "message": "Yükle" + }, "newDeviceVerificationNoticeContentPage1": { "message": "Şubat 2025 itibarıyla Bitwarden, yeni cihazlardan yeni girişleri doğrulamanız için e-posta adresinize bir kod gönderecektir." }, @@ -3567,5 +3712,35 @@ }, "move": { "message": "Taşı" + }, + "newLoginNudgeTitle": { + "message": "Save time with autofill" + }, + "newLoginNudgeBody": { + "message": "Include a Website so this login appears as an autofill suggestion." + }, + "newCardNudgeTitle": { + "message": "Seamless online checkout" + }, + "newCardNudgeBody": { + "message": "With cards, easily autofill payment forms securely and accurately." + }, + "newIdentityNudgeTitle": { + "message": "Simplify creating accounts" + }, + "newIdentityNudgeBody": { + "message": "With identities, quickly autofill long registration or contact forms." + }, + "newNoteNudgeTitle": { + "message": "Keep your sensitive data safe" + }, + "newNoteNudgeBody": { + "message": "With notes, securely store sensitive data like banking or insurance details." + }, + "newSshNudgeTitle": { + "message": "Developer-friendly SSH access" + }, + "newSshNudgeBody": { + "message": "Store your keys and connect with the SSH agent for fast, encrypted authentication." } } diff --git a/apps/desktop/src/locales/uk/messages.json b/apps/desktop/src/locales/uk/messages.json index f699dda8311..9b751b4830a 100644 --- a/apps/desktop/src/locales/uk/messages.json +++ b/apps/desktop/src/locales/uk/messages.json @@ -393,6 +393,64 @@ "authenticatorKeyTotp": { "message": "Ключ автентифікації (TOTP)" }, + "authenticatorKey": { + "message": "Ключ автентифікації" + }, + "autofillOptions": { + "message": "Параметри автозаповнення" + }, + "websiteUri": { + "message": "Вебсайт (URI)" + }, + "websiteUriCount": { + "message": "Вебсайт (URI) $COUNT$", + "description": "Label for an input field that contains a website URI. The input field is part of a list of fields, and the count indicates the position of the field in the list.", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + } + } + }, + "websiteAdded": { + "message": "Вебсайт додано" + }, + "addWebsite": { + "message": "Додати вебсайт" + }, + "deleteWebsite": { + "message": "Видалити вебсайт" + }, + "owner": { + "message": "Власник" + }, + "addField": { + "message": "Додати поле" + }, + "fieldType": { + "message": "Тип поля" + }, + "fieldLabel": { + "message": "Мітка поля" + }, + "add": { + "message": "Додати" + }, + "textHelpText": { + "message": "Використовуйте текстові поля для даних, як-от секретні запитання" + }, + "hiddenHelpText": { + "message": "Використовуйте приховані поля для конфіденційних даних, як-от пароль" + }, + "checkBoxHelpText": { + "message": "Використовуйте прапорці, якщо хочете автоматично заповнювати поля, як-от адресу е-пошти" + }, + "linkedHelpText": { + "message": "Використовуйте пов'язане поле у разі виникнення проблем з автозаповненням для певного вебсайту." + }, + "linkedLabelHelpText": { + "message": "Введіть html-ідентифікатор поля, назву, мітку або заповнювач." + }, "folder": { "message": "Тека" }, @@ -418,6 +476,9 @@ "message": "Пов'язано", "description": "This describes a field that is 'linked' (related) to another field." }, + "cfTypeCheckbox": { + "message": "Прапорець" + }, "linkedValue": { "message": "Пов'язане значення", "description": "This describes a value that is 'linked' (related) to another value." @@ -1915,6 +1976,43 @@ } } }, + "cardDetails": { + "message": "Подробиці картки" + }, + "cardBrandDetails": { + "message": "Подробиці $BRAND$", + "placeholders": { + "brand": { + "content": "$1", + "example": "Visa" + } + } + }, + "learnMoreAboutAuthenticators": { + "message": "Докладніше про програми автентифікації" + }, + "copyTOTP": { + "message": "Скопіюйте ключ автентифікації (TOTP)" + }, + "totpHelperTitle": { + "message": "Спростіть двоетапну перевірку" + }, + "totpHelper": { + "message": "Bitwarden може автоматично заповнювати одноразові коди двоетапної перевірки. Скопіюйте і вставте ключ у це поле." + }, + "totpHelperWithCapture": { + "message": "Bitwarden може автоматично заповнювати одноразові коди двоетапної перевірки. Відкрийте камеру, щоб сканувати QR-код на цьому вебсайті, або скопіюйте і вставте ключ у це поле." + }, + "premium": { + "message": "Преміум", + "description": "Premium membership" + }, + "cardExpiredTitle": { + "message": "Протермінована картка" + }, + "cardExpiredMessage": { + "message": "Якщо ви її поновили, оновіть інформацію" + }, "verificationRequired": { "message": "Потрібне підтвердження", "description": "Default title for the user verification dialog." @@ -2084,6 +2182,15 @@ "personalOwnershipPolicyInEffectImports": { "message": "Політика організації заблокувала імпортування записів до вашого особистого сховища." }, + "personalDetails": { + "message": "Особисті дані" + }, + "identification": { + "message": "Ідентифікація" + }, + "contactInfo": { + "message": "Контактна інформація" + }, "allSends": { "message": "Усі відправлення", "description": "'Sends' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." @@ -2470,7 +2577,7 @@ } }, "exportingIndividualVaultWithAttachmentsDescription": { - "message": "Будуть експортовані лише записи особистого сховища, включно з вкладеннями, пов'язані з $EMAIL$. Записи сховища організації не експортуватимуться.", + "message": "Будуть експортовані лише записи особистого сховища включно з вкладеннями, пов'язані з $EMAIL$. Записи сховища організації не експортуватимуться.", "placeholders": { "email": { "content": "$1", @@ -2518,6 +2625,9 @@ "generateEmail": { "message": "Генерувати е-пошту" }, + "usernameGenerator": { + "message": "Генератор імені користувача" + }, "spinboxBoundariesHint": { "message": "Значення має бути між $MIN$ та $MAX$.", "description": "Explains spin box minimum and maximum values to the user", @@ -3212,6 +3322,9 @@ "followTheStepsBelowToFinishLoggingIn": { "message": "Виконайте наведені нижче кроки, щоб завершити вхід." }, + "followTheStepsBelowToFinishLoggingInWithSecurityKey": { + "message": "Виконайте наведені нижче кроки, щоб завершити вхід за допомогою ключа безпеки." + }, "launchDuo": { "message": "Запустити Duo в браузері" }, @@ -3436,6 +3549,17 @@ "ssoError": { "message": "Не знайдено вільних портів для цього входу SSO." }, + "securePasswordGenerated": { + "message": "Надійний пароль згенеровано! Обов'язково оновіть свій пароль на вебсайті." + }, + "useGeneratorHelpTextPartOne": { + "message": "Скористайтеся генератором,", + "description": "This will be used as part of a larger sentence, broken up to include the generator icon. The full sentence will read 'Use the generator [GENERATOR_ICON] to create a strong unique password'" + }, + "useGeneratorHelpTextPartTwo": { + "message": "щоб створити надійний, унікальний пароль", + "description": "This will be used as part of a larger sentence, broken up to include the generator icon. The full sentence will read 'Use the generator [GENERATOR_ICON] to create a strong unique password'" + }, "biometricsStatusHelptextUnlockNeeded": { "message": "Біометричне розблокування недоступне, оскільки спочатку потрібно ввести PIN-код або пароль." }, @@ -3514,6 +3638,27 @@ "setupTwoStepLogin": { "message": "Налаштувати двоетапну перевірку" }, + "itemDetails": { + "message": "Подробиці запису" + }, + "itemName": { + "message": "Назва запису" + }, + "loginCredentials": { + "message": "Облікові дані для входу" + }, + "additionalOptions": { + "message": "Додаткові налаштування" + }, + "itemHistory": { + "message": "Історія запису" + }, + "lastEdited": { + "message": "Востаннє редаговано" + }, + "upload": { + "message": "Вивантажити" + }, "newDeviceVerificationNoticeContentPage1": { "message": "Bitwarden надсилатиме код підтвердження на електронну пошту вашого облікового запису під час входу з нових пристроїв, починаючи з лютого 2025 року." }, @@ -3567,5 +3712,35 @@ }, "move": { "message": "Перемістити" + }, + "newLoginNudgeTitle": { + "message": "Save time with autofill" + }, + "newLoginNudgeBody": { + "message": "Include a Website so this login appears as an autofill suggestion." + }, + "newCardNudgeTitle": { + "message": "Seamless online checkout" + }, + "newCardNudgeBody": { + "message": "With cards, easily autofill payment forms securely and accurately." + }, + "newIdentityNudgeTitle": { + "message": "Simplify creating accounts" + }, + "newIdentityNudgeBody": { + "message": "With identities, quickly autofill long registration or contact forms." + }, + "newNoteNudgeTitle": { + "message": "Keep your sensitive data safe" + }, + "newNoteNudgeBody": { + "message": "With notes, securely store sensitive data like banking or insurance details." + }, + "newSshNudgeTitle": { + "message": "Developer-friendly SSH access" + }, + "newSshNudgeBody": { + "message": "Store your keys and connect with the SSH agent for fast, encrypted authentication." } } diff --git a/apps/desktop/src/locales/vi/messages.json b/apps/desktop/src/locales/vi/messages.json index fa426f3de2f..d75175778a3 100644 --- a/apps/desktop/src/locales/vi/messages.json +++ b/apps/desktop/src/locales/vi/messages.json @@ -393,6 +393,64 @@ "authenticatorKeyTotp": { "message": "Khóa xác thực (TOTP)" }, + "authenticatorKey": { + "message": "Authenticator key" + }, + "autofillOptions": { + "message": "Autofill options" + }, + "websiteUri": { + "message": "Website (URI)" + }, + "websiteUriCount": { + "message": "Website (URI) $COUNT$", + "description": "Label for an input field that contains a website URI. The input field is part of a list of fields, and the count indicates the position of the field in the list.", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + } + } + }, + "websiteAdded": { + "message": "Website added" + }, + "addWebsite": { + "message": "Add website" + }, + "deleteWebsite": { + "message": "Delete website" + }, + "owner": { + "message": "Owner" + }, + "addField": { + "message": "Add field" + }, + "fieldType": { + "message": "Field type" + }, + "fieldLabel": { + "message": "Field label" + }, + "add": { + "message": "Add" + }, + "textHelpText": { + "message": "Use text fields for data like security questions" + }, + "hiddenHelpText": { + "message": "Use hidden fields for sensitive data like a password" + }, + "checkBoxHelpText": { + "message": "Use checkboxes if you'd like to autofill a form's checkbox, like a remember email" + }, + "linkedHelpText": { + "message": "Use a linked field when you are experiencing autofill issues for a specific website." + }, + "linkedLabelHelpText": { + "message": "Enter the the field's html id, name, aria-label, or placeholder." + }, "folder": { "message": "Thư mục" }, @@ -418,6 +476,9 @@ "message": "Đã liên kết", "description": "This describes a field that is 'linked' (related) to another field." }, + "cfTypeCheckbox": { + "message": "Checkbox" + }, "linkedValue": { "message": "Giá trị được liên kết", "description": "This describes a value that is 'linked' (related) to another value." @@ -1915,6 +1976,43 @@ } } }, + "cardDetails": { + "message": "Card details" + }, + "cardBrandDetails": { + "message": "$BRAND$ details", + "placeholders": { + "brand": { + "content": "$1", + "example": "Visa" + } + } + }, + "learnMoreAboutAuthenticators": { + "message": "Learn more about authenticators" + }, + "copyTOTP": { + "message": "Copy Authenticator key (TOTP)" + }, + "totpHelperTitle": { + "message": "Make 2-step verification seamless" + }, + "totpHelper": { + "message": "Bitwarden can store and fill 2-step verification codes. Copy and paste the key into this field." + }, + "totpHelperWithCapture": { + "message": "Bitwarden can store and fill 2-step verification codes. Select the camera icon to take a screenshot of this website's authenticator QR code, or copy and paste the key into this field." + }, + "premium": { + "message": "Premium", + "description": "Premium membership" + }, + "cardExpiredTitle": { + "message": "Expired card" + }, + "cardExpiredMessage": { + "message": "If you've renewed it, update the card's information" + }, "verificationRequired": { "message": "Yêu cầu xác minh", "description": "Default title for the user verification dialog." @@ -2084,6 +2182,15 @@ "personalOwnershipPolicyInEffectImports": { "message": "Chính sách của tổ chức đã chặn việc nhập các mục vào kho cá nhân của bạn." }, + "personalDetails": { + "message": "Personal details" + }, + "identification": { + "message": "Identification" + }, + "contactInfo": { + "message": "Contact information" + }, "allSends": { "message": "Tất cả mục Gửi", "description": "'Sends' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." @@ -2518,6 +2625,9 @@ "generateEmail": { "message": "Generate email" }, + "usernameGenerator": { + "message": "Username generator" + }, "spinboxBoundariesHint": { "message": "Value must be between $MIN$ and $MAX$.", "description": "Explains spin box minimum and maximum values to the user", @@ -3212,6 +3322,9 @@ "followTheStepsBelowToFinishLoggingIn": { "message": "Follow the steps below to finish logging in." }, + "followTheStepsBelowToFinishLoggingInWithSecurityKey": { + "message": "Follow the steps below to finish logging in with your security key." + }, "launchDuo": { "message": "Khởi chạy Duo trong trình duyệt" }, @@ -3436,6 +3549,17 @@ "ssoError": { "message": "Không thể tìm thấy cổng trống để đăng nhập SSO." }, + "securePasswordGenerated": { + "message": "Secure password generated! Don't forget to also update your password on the website." + }, + "useGeneratorHelpTextPartOne": { + "message": "Use the generator", + "description": "This will be used as part of a larger sentence, broken up to include the generator icon. The full sentence will read 'Use the generator [GENERATOR_ICON] to create a strong unique password'" + }, + "useGeneratorHelpTextPartTwo": { + "message": "to create a strong unique password", + "description": "This will be used as part of a larger sentence, broken up to include the generator icon. The full sentence will read 'Use the generator [GENERATOR_ICON] to create a strong unique password'" + }, "biometricsStatusHelptextUnlockNeeded": { "message": "Biometric unlock is unavailable because PIN or password unlock is required first." }, @@ -3514,6 +3638,27 @@ "setupTwoStepLogin": { "message": "Set up two-step login" }, + "itemDetails": { + "message": "Item details" + }, + "itemName": { + "message": "Item name" + }, + "loginCredentials": { + "message": "Login credentials" + }, + "additionalOptions": { + "message": "Additional options" + }, + "itemHistory": { + "message": "Item history" + }, + "lastEdited": { + "message": "Last edited" + }, + "upload": { + "message": "Upload" + }, "newDeviceVerificationNoticeContentPage1": { "message": "Bitwarden will send a code to your account email to verify logins from new devices starting in February 2025." }, @@ -3567,5 +3712,35 @@ }, "move": { "message": "Move" + }, + "newLoginNudgeTitle": { + "message": "Save time with autofill" + }, + "newLoginNudgeBody": { + "message": "Include a Website so this login appears as an autofill suggestion." + }, + "newCardNudgeTitle": { + "message": "Seamless online checkout" + }, + "newCardNudgeBody": { + "message": "With cards, easily autofill payment forms securely and accurately." + }, + "newIdentityNudgeTitle": { + "message": "Simplify creating accounts" + }, + "newIdentityNudgeBody": { + "message": "With identities, quickly autofill long registration or contact forms." + }, + "newNoteNudgeTitle": { + "message": "Keep your sensitive data safe" + }, + "newNoteNudgeBody": { + "message": "With notes, securely store sensitive data like banking or insurance details." + }, + "newSshNudgeTitle": { + "message": "Developer-friendly SSH access" + }, + "newSshNudgeBody": { + "message": "Store your keys and connect with the SSH agent for fast, encrypted authentication." } } diff --git a/apps/desktop/src/locales/zh_CN/messages.json b/apps/desktop/src/locales/zh_CN/messages.json index 38892deb282..7085d4aa033 100644 --- a/apps/desktop/src/locales/zh_CN/messages.json +++ b/apps/desktop/src/locales/zh_CN/messages.json @@ -393,6 +393,64 @@ "authenticatorKeyTotp": { "message": "验证器密钥 (TOTP)" }, + "authenticatorKey": { + "message": "Authenticator key" + }, + "autofillOptions": { + "message": "Autofill options" + }, + "websiteUri": { + "message": "Website (URI)" + }, + "websiteUriCount": { + "message": "Website (URI) $COUNT$", + "description": "Label for an input field that contains a website URI. The input field is part of a list of fields, and the count indicates the position of the field in the list.", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + } + } + }, + "websiteAdded": { + "message": "Website added" + }, + "addWebsite": { + "message": "Add website" + }, + "deleteWebsite": { + "message": "Delete website" + }, + "owner": { + "message": "Owner" + }, + "addField": { + "message": "Add field" + }, + "fieldType": { + "message": "Field type" + }, + "fieldLabel": { + "message": "Field label" + }, + "add": { + "message": "Add" + }, + "textHelpText": { + "message": "Use text fields for data like security questions" + }, + "hiddenHelpText": { + "message": "Use hidden fields for sensitive data like a password" + }, + "checkBoxHelpText": { + "message": "Use checkboxes if you'd like to autofill a form's checkbox, like a remember email" + }, + "linkedHelpText": { + "message": "Use a linked field when you are experiencing autofill issues for a specific website." + }, + "linkedLabelHelpText": { + "message": "Enter the the field's html id, name, aria-label, or placeholder." + }, "folder": { "message": "文件夹" }, @@ -418,6 +476,9 @@ "message": "链接型", "description": "This describes a field that is 'linked' (related) to another field." }, + "cfTypeCheckbox": { + "message": "Checkbox" + }, "linkedValue": { "message": "链接的值", "description": "This describes a value that is 'linked' (related) to another value." @@ -1915,6 +1976,43 @@ } } }, + "cardDetails": { + "message": "Card details" + }, + "cardBrandDetails": { + "message": "$BRAND$ details", + "placeholders": { + "brand": { + "content": "$1", + "example": "Visa" + } + } + }, + "learnMoreAboutAuthenticators": { + "message": "Learn more about authenticators" + }, + "copyTOTP": { + "message": "Copy Authenticator key (TOTP)" + }, + "totpHelperTitle": { + "message": "Make 2-step verification seamless" + }, + "totpHelper": { + "message": "Bitwarden 可以存储并填充两步验证码。将密钥复制并粘贴到此字段。" + }, + "totpHelperWithCapture": { + "message": "Bitwarden 可以存储并填充两步验证码。选择相机图标来拍摄此网站的验证器二维码,或将密钥复制并粘贴到此字段。" + }, + "premium": { + "message": "Premium", + "description": "Premium membership" + }, + "cardExpiredTitle": { + "message": "Expired card" + }, + "cardExpiredMessage": { + "message": "If you've renewed it, update the card's information" + }, "verificationRequired": { "message": "需要验证", "description": "Default title for the user verification dialog." @@ -2084,6 +2182,15 @@ "personalOwnershipPolicyInEffectImports": { "message": "组织策略已阻止将项目导入您的个人密码库。" }, + "personalDetails": { + "message": "Personal details" + }, + "identification": { + "message": "Identification" + }, + "contactInfo": { + "message": "联系信息" + }, "allSends": { "message": "所有的 Send", "description": "'Sends' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." @@ -2518,6 +2625,9 @@ "generateEmail": { "message": "生成电子邮箱" }, + "usernameGenerator": { + "message": "Username generator" + }, "spinboxBoundariesHint": { "message": "值必须在 $MIN$ 和 $MAX$ 之间。", "description": "Explains spin box minimum and maximum values to the user", @@ -3212,6 +3322,9 @@ "followTheStepsBelowToFinishLoggingIn": { "message": "按照以下步骤完成登录。" }, + "followTheStepsBelowToFinishLoggingInWithSecurityKey": { + "message": "请按照下面的步骤,使用您的安全密钥完成登录。" + }, "launchDuo": { "message": "在浏览器中启动 Duo" }, @@ -3436,6 +3549,17 @@ "ssoError": { "message": "未找到用于 SSO 登录的空闲端口。" }, + "securePasswordGenerated": { + "message": "安全的密码已生成!不要忘记在网站上更新您的密码。" + }, + "useGeneratorHelpTextPartOne": { + "message": "Use the generator", + "description": "This will be used as part of a larger sentence, broken up to include the generator icon. The full sentence will read 'Use the generator [GENERATOR_ICON] to create a strong unique password'" + }, + "useGeneratorHelpTextPartTwo": { + "message": "to create a strong unique password", + "description": "This will be used as part of a larger sentence, broken up to include the generator icon. The full sentence will read 'Use the generator [GENERATOR_ICON] to create a strong unique password'" + }, "biometricsStatusHelptextUnlockNeeded": { "message": "生物识别解锁不可用,因为需要先使用 PIN 或密码解锁。" }, @@ -3514,6 +3638,27 @@ "setupTwoStepLogin": { "message": "设置两步登录" }, + "itemDetails": { + "message": "Item details" + }, + "itemName": { + "message": "Item name" + }, + "loginCredentials": { + "message": "Login credentials" + }, + "additionalOptions": { + "message": "Additional options" + }, + "itemHistory": { + "message": "Item history" + }, + "lastEdited": { + "message": "最后编辑于" + }, + "upload": { + "message": "上传" + }, "newDeviceVerificationNoticeContentPage1": { "message": "从 2025 年 02 月起,当有来自新设备的登录时,Bitwarden 将向您的账户电子邮箱发送验证码。" }, @@ -3567,5 +3712,35 @@ }, "move": { "message": "移动" + }, + "newLoginNudgeTitle": { + "message": "Save time with autofill" + }, + "newLoginNudgeBody": { + "message": "Include a Website so this login appears as an autofill suggestion." + }, + "newCardNudgeTitle": { + "message": "Seamless online checkout" + }, + "newCardNudgeBody": { + "message": "With cards, easily autofill payment forms securely and accurately." + }, + "newIdentityNudgeTitle": { + "message": "Simplify creating accounts" + }, + "newIdentityNudgeBody": { + "message": "With identities, quickly autofill long registration or contact forms." + }, + "newNoteNudgeTitle": { + "message": "Keep your sensitive data safe" + }, + "newNoteNudgeBody": { + "message": "With notes, securely store sensitive data like banking or insurance details." + }, + "newSshNudgeTitle": { + "message": "Developer-friendly SSH access" + }, + "newSshNudgeBody": { + "message": "Store your keys and connect with the SSH agent for fast, encrypted authentication." } } diff --git a/apps/desktop/src/locales/zh_TW/messages.json b/apps/desktop/src/locales/zh_TW/messages.json index 8e3caf38218..a871a07adda 100644 --- a/apps/desktop/src/locales/zh_TW/messages.json +++ b/apps/desktop/src/locales/zh_TW/messages.json @@ -393,6 +393,64 @@ "authenticatorKeyTotp": { "message": "驗證器金鑰 (TOTP)" }, + "authenticatorKey": { + "message": "Authenticator key" + }, + "autofillOptions": { + "message": "Autofill options" + }, + "websiteUri": { + "message": "Website (URI)" + }, + "websiteUriCount": { + "message": "Website (URI) $COUNT$", + "description": "Label for an input field that contains a website URI. The input field is part of a list of fields, and the count indicates the position of the field in the list.", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + } + } + }, + "websiteAdded": { + "message": "Website added" + }, + "addWebsite": { + "message": "Add website" + }, + "deleteWebsite": { + "message": "Delete website" + }, + "owner": { + "message": "Owner" + }, + "addField": { + "message": "Add field" + }, + "fieldType": { + "message": "Field type" + }, + "fieldLabel": { + "message": "Field label" + }, + "add": { + "message": "Add" + }, + "textHelpText": { + "message": "Use text fields for data like security questions" + }, + "hiddenHelpText": { + "message": "Use hidden fields for sensitive data like a password" + }, + "checkBoxHelpText": { + "message": "Use checkboxes if you'd like to autofill a form's checkbox, like a remember email" + }, + "linkedHelpText": { + "message": "Use a linked field when you are experiencing autofill issues for a specific website." + }, + "linkedLabelHelpText": { + "message": "Enter the the field's html id, name, aria-label, or placeholder." + }, "folder": { "message": "資料夾" }, @@ -418,6 +476,9 @@ "message": "連結型", "description": "This describes a field that is 'linked' (related) to another field." }, + "cfTypeCheckbox": { + "message": "Checkbox" + }, "linkedValue": { "message": "連結的數值", "description": "This describes a value that is 'linked' (related) to another value." @@ -1915,6 +1976,43 @@ } } }, + "cardDetails": { + "message": "Card details" + }, + "cardBrandDetails": { + "message": "$BRAND$ details", + "placeholders": { + "brand": { + "content": "$1", + "example": "Visa" + } + } + }, + "learnMoreAboutAuthenticators": { + "message": "Learn more about authenticators" + }, + "copyTOTP": { + "message": "Copy Authenticator key (TOTP)" + }, + "totpHelperTitle": { + "message": "Make 2-step verification seamless" + }, + "totpHelper": { + "message": "Bitwarden can store and fill 2-step verification codes. Copy and paste the key into this field." + }, + "totpHelperWithCapture": { + "message": "Bitwarden can store and fill 2-step verification codes. Select the camera icon to take a screenshot of this website's authenticator QR code, or copy and paste the key into this field." + }, + "premium": { + "message": "Premium", + "description": "Premium membership" + }, + "cardExpiredTitle": { + "message": "Expired card" + }, + "cardExpiredMessage": { + "message": "If you've renewed it, update the card's information" + }, "verificationRequired": { "message": "需要驗證", "description": "Default title for the user verification dialog." @@ -2084,6 +2182,15 @@ "personalOwnershipPolicyInEffectImports": { "message": "某個組織政策已禁止您將項目匯入至您的個人密碼庫。" }, + "personalDetails": { + "message": "Personal details" + }, + "identification": { + "message": "Identification" + }, + "contactInfo": { + "message": "Contact information" + }, "allSends": { "message": "所有 Send", "description": "'Sends' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." @@ -2518,6 +2625,9 @@ "generateEmail": { "message": "Generate email" }, + "usernameGenerator": { + "message": "Username generator" + }, "spinboxBoundariesHint": { "message": "Value must be between $MIN$ and $MAX$.", "description": "Explains spin box minimum and maximum values to the user", @@ -3212,6 +3322,9 @@ "followTheStepsBelowToFinishLoggingIn": { "message": "Follow the steps below to finish logging in." }, + "followTheStepsBelowToFinishLoggingInWithSecurityKey": { + "message": "Follow the steps below to finish logging in with your security key." + }, "launchDuo": { "message": "使用瀏覽器啟動 Duo" }, @@ -3436,6 +3549,17 @@ "ssoError": { "message": "無法找到可用於 SSO 登入的空閒連接埠。" }, + "securePasswordGenerated": { + "message": "Secure password generated! Don't forget to also update your password on the website." + }, + "useGeneratorHelpTextPartOne": { + "message": "Use the generator", + "description": "This will be used as part of a larger sentence, broken up to include the generator icon. The full sentence will read 'Use the generator [GENERATOR_ICON] to create a strong unique password'" + }, + "useGeneratorHelpTextPartTwo": { + "message": "to create a strong unique password", + "description": "This will be used as part of a larger sentence, broken up to include the generator icon. The full sentence will read 'Use the generator [GENERATOR_ICON] to create a strong unique password'" + }, "biometricsStatusHelptextUnlockNeeded": { "message": "需要 PIN 碼或密碼解鎖才能使用生物辨識解鎖。" }, @@ -3514,6 +3638,27 @@ "setupTwoStepLogin": { "message": "啟動兩階段登入" }, + "itemDetails": { + "message": "Item details" + }, + "itemName": { + "message": "Item name" + }, + "loginCredentials": { + "message": "Login credentials" + }, + "additionalOptions": { + "message": "Additional options" + }, + "itemHistory": { + "message": "Item history" + }, + "lastEdited": { + "message": "Last edited" + }, + "upload": { + "message": "Upload" + }, "newDeviceVerificationNoticeContentPage1": { "message": "從 2025 年 2 月開始,Bitwarden 會傳送代碼到您的帳號電子郵件中來驗證新裝置的登入。" }, @@ -3567,5 +3712,35 @@ }, "move": { "message": "Move" + }, + "newLoginNudgeTitle": { + "message": "Save time with autofill" + }, + "newLoginNudgeBody": { + "message": "Include a Website so this login appears as an autofill suggestion." + }, + "newCardNudgeTitle": { + "message": "Seamless online checkout" + }, + "newCardNudgeBody": { + "message": "With cards, easily autofill payment forms securely and accurately." + }, + "newIdentityNudgeTitle": { + "message": "Simplify creating accounts" + }, + "newIdentityNudgeBody": { + "message": "With identities, quickly autofill long registration or contact forms." + }, + "newNoteNudgeTitle": { + "message": "Keep your sensitive data safe" + }, + "newNoteNudgeBody": { + "message": "With notes, securely store sensitive data like banking or insurance details." + }, + "newSshNudgeTitle": { + "message": "Developer-friendly SSH access" + }, + "newSshNudgeBody": { + "message": "Store your keys and connect with the SSH agent for fast, encrypted authentication." } } diff --git a/apps/desktop/src/main/menu/menu.file.ts b/apps/desktop/src/main/menu/menu.file.ts index 712e579515e..25db5b695e7 100644 --- a/apps/desktop/src/main/menu/menu.file.ts +++ b/apps/desktop/src/main/menu/menu.file.ts @@ -103,6 +103,12 @@ export class FileMenu extends FirstMenu implements IMenubarMenu { click: () => this.sendMessage("newSecureNote"), accelerator: "CmdOrCtrl+Shift+S", }, + { + id: "typeSshKey", + label: this.localize("typeSshKey"), + click: () => this.sendMessage("newSshKey"), + accelerator: "CmdOrCtrl+Shift+K", + }, ]; } diff --git a/apps/desktop/src/package-lock.json b/apps/desktop/src/package-lock.json index a720dff7257..b3a33dc75e3 100644 --- a/apps/desktop/src/package-lock.json +++ b/apps/desktop/src/package-lock.json @@ -1,12 +1,12 @@ { "name": "@bitwarden/desktop", - "version": "2025.4.1", + "version": "2025.5.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@bitwarden/desktop", - "version": "2025.4.1", + "version": "2025.5.0", "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 e288f5f5a79..c180ed8c744 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": "2025.4.1", + "version": "2025.5.0", "author": "Bitwarden Inc. (https://bitwarden.com)", "homepage": "https://bitwarden.com", "license": "GPL-3.0", diff --git a/apps/desktop/src/platform/services/electron-log.main.service.ts b/apps/desktop/src/platform/services/electron-log.main.service.ts index d7100b54825..947f4449271 100644 --- a/apps/desktop/src/platform/services/electron-log.main.service.ts +++ b/apps/desktop/src/platform/services/electron-log.main.service.ts @@ -7,6 +7,7 @@ import log from "electron-log/main"; import { LogLevelType } from "@bitwarden/common/platform/enums/log-level-type.enum"; import { ConsoleLogService as BaseLogService } from "@bitwarden/common/platform/services/console-log.service"; +import { logging } from "@bitwarden/desktop-napi"; import { isDev } from "../../utils"; @@ -30,6 +31,29 @@ export class ElectronLogMainService extends BaseLogService { ipcMain.handle("ipc.log", (_event, { level, message, optionalParams }) => { this.write(level, message, ...optionalParams); }); + + logging.initNapiLog((error, level, message) => this.writeNapiLog(level, message)); + } + + private writeNapiLog(level: logging.LogLevel, message: string) { + let levelType: LogLevelType; + + switch (level) { + case logging.LogLevel.Debug: + levelType = LogLevelType.Debug; + break; + case logging.LogLevel.Warn: + levelType = LogLevelType.Warning; + break; + case logging.LogLevel.Error: + levelType = LogLevelType.Error; + break; + default: + levelType = LogLevelType.Info; + break; + } + + this.write(levelType, "[NAPI] " + message); } write(level: LogLevelType, message?: any, ...optionalParams: any[]) { diff --git a/apps/desktop/src/platform/services/electron-log.service.spec.ts b/apps/desktop/src/platform/services/electron-log.service.spec.ts index 918508977fd..db3093e08e2 100644 --- a/apps/desktop/src/platform/services/electron-log.service.spec.ts +++ b/apps/desktop/src/platform/services/electron-log.service.spec.ts @@ -5,6 +5,14 @@ jest.mock("electron", () => ({ ipcMain: { handle: jest.fn(), on: jest.fn() }, })); +jest.mock("@bitwarden/desktop-napi", () => { + return { + logging: { + initNapiLog: jest.fn(), + }, + }; +}); + describe("ElectronLogMainService", () => { it("sets dev based on electron method", () => { process.env.ELECTRON_IS_DEV = "1"; diff --git a/apps/desktop/src/scss/base.scss b/apps/desktop/src/scss/base.scss index 22eb3df0d17..494e91529ee 100644 --- a/apps/desktop/src/scss/base.scss +++ b/apps/desktop/src/scss/base.scss @@ -147,3 +147,8 @@ div:not(.modal)::-webkit-scrollbar-thumb, .mx-auto { margin-left: auto !important; } + +.vault-v2 button:not([bitbutton]):not([biticonbutton]) i.bwi, +a i.bwi { + margin-right: 0.25rem; +} diff --git a/apps/desktop/src/scss/vault.scss b/apps/desktop/src/scss/vault.scss index f7403ad62d2..88216a2b926 100644 --- a/apps/desktop/src/scss/vault.scss +++ b/apps/desktop/src/scss/vault.scss @@ -162,3 +162,7 @@ app-root { } } } + +.vault-v2 > .details { + flex-direction: column-reverse; +} diff --git a/apps/desktop/src/services/desktop-cipher-form-generator.service.ts b/apps/desktop/src/services/desktop-cipher-form-generator.service.ts new file mode 100644 index 00000000000..8a33f4ced0a --- /dev/null +++ b/apps/desktop/src/services/desktop-cipher-form-generator.service.ts @@ -0,0 +1,32 @@ +import { inject, Injectable } from "@angular/core"; +import { firstValueFrom } from "rxjs"; + +import { DialogService } from "@bitwarden/components"; +import { CipherFormGenerationService } from "@bitwarden/vault"; + +import { CredentialGeneratorDialogComponent } from "../vault/app/vault/credential-generator-dialog.component"; + +@Injectable() +export class DesktopCredentialGenerationService implements CipherFormGenerationService { + private dialogService = inject(DialogService); + + async generatePassword(): Promise { + return await this.generateCredential("password"); + } + + async generateUsername(uri: string): Promise { + return await this.generateCredential("username", uri); + } + + async generateCredential(type: "password" | "username", uri?: string): Promise { + const dialogRef = CredentialGeneratorDialogComponent.open(this.dialogService, { type, uri }); + + const result = await firstValueFrom(dialogRef.closed); + + if (!result || result.action === "canceled" || !result.generatedValue) { + return ""; + } + + return result.generatedValue; + } +} diff --git a/apps/desktop/src/services/desktop-premium-upgrade-prompt.service.spec.ts b/apps/desktop/src/services/desktop-premium-upgrade-prompt.service.spec.ts new file mode 100644 index 00000000000..3b33116ea5a --- /dev/null +++ b/apps/desktop/src/services/desktop-premium-upgrade-prompt.service.spec.ts @@ -0,0 +1,30 @@ +import { TestBed } from "@angular/core/testing"; +import { mock, MockProxy } from "jest-mock-extended"; + +import { MessagingService } from "@bitwarden/common/platform/abstractions/messaging.service"; + +import { DesktopPremiumUpgradePromptService } from "./desktop-premium-upgrade-prompt.service"; + +describe("DesktopPremiumUpgradePromptService", () => { + let service: DesktopPremiumUpgradePromptService; + let messager: MockProxy; + + beforeEach(async () => { + messager = mock(); + await TestBed.configureTestingModule({ + providers: [ + DesktopPremiumUpgradePromptService, + { provide: MessagingService, useValue: messager }, + ], + }).compileComponents(); + + service = TestBed.inject(DesktopPremiumUpgradePromptService); + }); + + describe("promptForPremium", () => { + it("navigates to the premium update screen", async () => { + await service.promptForPremium(); + expect(messager.send).toHaveBeenCalledWith("openPremium"); + }); + }); +}); diff --git a/apps/desktop/src/services/desktop-premium-upgrade-prompt.service.ts b/apps/desktop/src/services/desktop-premium-upgrade-prompt.service.ts new file mode 100644 index 00000000000..f2375ecfebb --- /dev/null +++ b/apps/desktop/src/services/desktop-premium-upgrade-prompt.service.ts @@ -0,0 +1,15 @@ +import { inject } from "@angular/core"; + +import { MessagingService } from "@bitwarden/common/platform/abstractions/messaging.service"; +import { PremiumUpgradePromptService } from "@bitwarden/common/vault/abstractions/premium-upgrade-prompt.service"; + +/** + * This class handles the premium upgrade process for the desktop. + */ +export class DesktopPremiumUpgradePromptService implements PremiumUpgradePromptService { + private messagingService = inject(MessagingService); + + async promptForPremium() { + this.messagingService.send("openPremium"); + } +} diff --git a/apps/desktop/src/vault/app/vault/credential-generator-dialog.component.html b/apps/desktop/src/vault/app/vault/credential-generator-dialog.component.html index 47232dff66d..31f47d824d6 100644 --- a/apps/desktop/src/vault/app/vault/credential-generator-dialog.component.html +++ b/apps/desktop/src/vault/app/vault/credential-generator-dialog.component.html @@ -3,6 +3,7 @@ @@ -27,7 +28,6 @@ (click)="applyCredentials()" appA11yTitle="{{ buttonLabel }}" bitButton - bitDialogClose [disabled]="!(buttonLabel && credentialValue)" > {{ buttonLabel }} diff --git a/apps/desktop/src/vault/app/vault/credential-generator-dialog.component.ts b/apps/desktop/src/vault/app/vault/credential-generator-dialog.component.ts index eda35a8c76d..2858d7330e5 100644 --- a/apps/desktop/src/vault/app/vault/credential-generator-dialog.component.ts +++ b/apps/desktop/src/vault/app/vault/credential-generator-dialog.component.ts @@ -10,6 +10,7 @@ import { DialogService, ItemModule, LinkModule, + DialogRef, } from "@bitwarden/components"; import { CredentialGeneratorHistoryDialogComponent, @@ -19,10 +20,22 @@ import { AlgorithmInfo } from "@bitwarden/generator-core"; import { CipherFormGeneratorComponent } from "@bitwarden/vault"; type CredentialGeneratorParams = { - onCredentialGenerated: (value?: string) => void; + /** @deprecated Prefer use of dialogRef.closed to retreive the generated value */ + onCredentialGenerated?: (value?: string) => void; type: "password" | "username"; + uri?: string; }; +export interface CredentialGeneratorDialogResult { + action: CredentialGeneratorDialogAction; + generatedValue?: string; +} + +export enum CredentialGeneratorDialogAction { + Selected = "selected", + Canceled = "canceled", +} + @Component({ standalone: true, selector: "credential-generator-dialog", @@ -45,6 +58,7 @@ export class CredentialGeneratorDialogComponent { constructor( @Inject(DIALOG_DATA) protected data: CredentialGeneratorParams, private dialogService: DialogService, + private dialogRef: DialogRef, private i18nService: I18nService, ) {} @@ -59,11 +73,15 @@ export class CredentialGeneratorDialogComponent { }; applyCredentials = () => { - this.data.onCredentialGenerated(this.credentialValue); + this.data.onCredentialGenerated?.(this.credentialValue); + this.dialogRef.close({ + action: CredentialGeneratorDialogAction.Selected, + generatedValue: this.credentialValue, + }); }; clearCredentials = () => { - this.data.onCredentialGenerated(); + this.data.onCredentialGenerated?.(); }; onCredentialGenerated = (value: string) => { @@ -75,9 +93,12 @@ export class CredentialGeneratorDialogComponent { this.dialogService.open(CredentialGeneratorHistoryDialogComponent); }; - static open = (dialogService: DialogService, data: CredentialGeneratorParams) => { - dialogService.open(CredentialGeneratorDialogComponent, { - data, - }); - }; + static open(dialogService: DialogService, data: CredentialGeneratorParams) { + return dialogService.open( + CredentialGeneratorDialogComponent, + { + data, + }, + ); + } } diff --git a/apps/desktop/src/vault/app/vault/item-footer.component.html b/apps/desktop/src/vault/app/vault/item-footer.component.html new file mode 100644 index 00000000000..6915555c08b --- /dev/null +++ b/apps/desktop/src/vault/app/vault/item-footer.component.html @@ -0,0 +1,64 @@ + diff --git a/apps/desktop/src/vault/app/vault/item-footer.component.ts b/apps/desktop/src/vault/app/vault/item-footer.component.ts new file mode 100644 index 00000000000..639d1557ecd --- /dev/null +++ b/apps/desktop/src/vault/app/vault/item-footer.component.ts @@ -0,0 +1,159 @@ +import { CommonModule } from "@angular/common"; +import { Input, Output, EventEmitter, Component, OnInit, ViewChild } from "@angular/core"; +import { Observable, 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 { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; +import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; +import { CollectionId, UserId } from "@bitwarden/common/types/guid"; +import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service"; +import { CipherRepromptType } 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 { ButtonComponent, ButtonModule, DialogService, ToastService } from "@bitwarden/components"; +import { PasswordRepromptService } from "@bitwarden/vault"; + +@Component({ + selector: "app-vault-item-footer", + templateUrl: "item-footer.component.html", + standalone: true, + imports: [ButtonModule, CommonModule, JslibModule], +}) +export class ItemFooterComponent implements OnInit { + @Input({ required: true }) cipher: CipherView = new CipherView(); + @Input() collectionId: string | null = null; + @Input({ required: true }) action: string = "view"; + @Input() isSubmitting: boolean = false; + @Output() onEdit = new EventEmitter(); + @Output() onClone = new EventEmitter(); + @Output() onDelete = new EventEmitter(); + @Output() onRestore = new EventEmitter(); + @Output() onCancel = new EventEmitter(); + @ViewChild("submitBtn", { static: false }) submitBtn: ButtonComponent | null = null; + + canDeleteCipher$: Observable = new Observable(); + activeUserId: UserId | null = null; + + private passwordReprompted = false; + + constructor( + protected cipherService: CipherService, + protected dialogService: DialogService, + protected passwordRepromptService: PasswordRepromptService, + protected cipherAuthorizationService: CipherAuthorizationService, + protected accountService: AccountService, + protected toastService: ToastService, + protected i18nService: I18nService, + protected logService: LogService, + ) {} + + async ngOnInit() { + this.canDeleteCipher$ = this.cipherAuthorizationService.canDeleteCipher$(this.cipher, [ + this.collectionId as CollectionId, + ]); + this.activeUserId = await firstValueFrom(this.accountService.activeAccount$.pipe(getUserId)); + } + + async clone() { + if (this.cipher.login?.hasFido2Credentials) { + const confirmed = await this.dialogService.openSimpleDialog({ + title: { key: "passkeyNotCopied" }, + content: { key: "passkeyNotCopiedAlert" }, + type: "info", + }); + + if (!confirmed) { + return false; + } + } + + if (await this.promptPassword()) { + this.onClone.emit(this.cipher); + return true; + } + + return false; + } + + protected edit() { + this.onEdit.emit(this.cipher); + } + + cancel() { + this.onCancel.emit(this.cipher); + } + + async delete(): Promise { + if (!(await this.promptPassword())) { + return false; + } + + const confirmed = await this.dialogService.openSimpleDialog({ + title: { key: "deleteItem" }, + content: { + key: this.cipher.isDeleted ? "permanentlyDeleteItemConfirmation" : "deleteItemConfirmation", + }, + type: "warning", + }); + + if (!confirmed) { + return false; + } + + try { + const activeUserId = await firstValueFrom(this.accountService.activeAccount$.pipe(getUserId)); + await this.deleteCipher(activeUserId); + this.toastService.showToast({ + variant: "success", + message: this.i18nService.t( + this.cipher.isDeleted ? "permanentlyDeletedItem" : "deletedItem", + ), + }); + this.onDelete.emit(this.cipher); + } catch (e) { + this.logService.error(e); + } + + return true; + } + + async restore(): Promise { + if (!this.cipher.isDeleted) { + return false; + } + + try { + const activeUserId = await firstValueFrom(this.accountService.activeAccount$.pipe(getUserId)); + await this.restoreCipher(activeUserId); + this.toastService.showToast({ + variant: "success", + message: this.i18nService.t("restoredItem"), + }); + this.onRestore.emit(this.cipher); + } catch (e) { + this.logService.error(e); + } + + return true; + } + + protected deleteCipher(userId: UserId) { + return this.cipher.isDeleted + ? this.cipherService.deleteWithServer(this.cipher.id, userId) + : this.cipherService.softDeleteWithServer(this.cipher.id, userId); + } + + protected restoreCipher(userId: UserId) { + return this.cipherService.restoreWithServer(this.cipher.id, userId); + } + + protected async promptPassword() { + if (this.cipher.reprompt === CipherRepromptType.None || this.passwordReprompted) { + return true; + } + + return (this.passwordReprompted = await this.passwordRepromptService.showPasswordPrompt()); + } +} diff --git a/apps/desktop/src/vault/app/vault/vault-filter/vault-filter.module.ts b/apps/desktop/src/vault/app/vault/vault-filter/vault-filter.module.ts index fb6706bef1c..8729996c835 100644 --- a/apps/desktop/src/vault/app/vault/vault-filter/vault-filter.module.ts +++ b/apps/desktop/src/vault/app/vault/vault-filter/vault-filter.module.ts @@ -1,5 +1,5 @@ +import { CommonModule } from "@angular/common"; import { NgModule } from "@angular/core"; -import { BrowserModule } from "@angular/platform-browser"; import { JslibModule } from "@bitwarden/angular/jslib.module"; import { DeprecatedVaultFilterService as DeprecatedVaultFilterServiceAbstraction } from "@bitwarden/angular/vault/abstractions/deprecated-vault-filter.service"; @@ -13,7 +13,7 @@ import { TypeFilterComponent } from "./filters/type-filter.component"; import { VaultFilterComponent } from "./vault-filter.component"; @NgModule({ - imports: [BrowserModule, JslibModule], + imports: [CommonModule, JslibModule], declarations: [ VaultFilterComponent, CollectionFilterComponent, diff --git a/apps/desktop/src/vault/app/vault/vault-items-v2.component.html b/apps/desktop/src/vault/app/vault/vault-items-v2.component.html new file mode 100644 index 00000000000..63e648e3cf3 --- /dev/null +++ b/apps/desktop/src/vault/app/vault/vault-items-v2.component.html @@ -0,0 +1,96 @@ + + + + + + + + + + + + + {{ c.name }} + + + {{ "shared" | i18n }} + + + + {{ "attachments" | i18n }} + + + + {{ c.subTitle }} + + + + + + + {{ "noItemsInList" | i18n }} + + + + + + + + + + + + + + {{ "typeLogin" | i18n }} + + + + {{ "typeCard" | i18n }} + + + + {{ "typeIdentity" | i18n }} + + + + {{ "typeSecureNote" | i18n }} + + + + {{ "typeSshKey" | i18n }} + + + diff --git a/apps/desktop/src/vault/app/vault/vault-items-v2.component.ts b/apps/desktop/src/vault/app/vault/vault-items-v2.component.ts new file mode 100644 index 00000000000..31d4098d2b2 --- /dev/null +++ b/apps/desktop/src/vault/app/vault/vault-items-v2.component.ts @@ -0,0 +1,42 @@ +import { ScrollingModule } from "@angular/cdk/scrolling"; +import { CommonModule } from "@angular/common"; +import { Component } from "@angular/core"; +import { takeUntilDestroyed } from "@angular/core/rxjs-interop"; +import { distinctUntilChanged } from "rxjs"; + +import { JslibModule } from "@bitwarden/angular/jslib.module"; +import { VaultItemsComponent as BaseVaultItemsComponent } from "@bitwarden/angular/vault/components/vault-items.component"; +import { SearchService } from "@bitwarden/common/abstractions/search.service"; +import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; +import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service"; +import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view"; +import { MenuModule } from "@bitwarden/components"; + +import { SearchBarService } from "../../../app/layout/search/search-bar.service"; + +@Component({ + selector: "app-vault-items-v2", + templateUrl: "vault-items-v2.component.html", + standalone: true, + imports: [MenuModule, CommonModule, JslibModule, ScrollingModule], +}) +export class VaultItemsV2Component extends BaseVaultItemsComponent { + constructor( + searchService: SearchService, + private readonly searchBarService: SearchBarService, + cipherService: CipherService, + accountService: AccountService, + ) { + super(searchService, cipherService, accountService); + + this.searchBarService.searchText$ + .pipe(distinctUntilChanged(), takeUntilDestroyed()) + .subscribe((searchText) => { + this.searchText = searchText!; + }); + } + + trackByFn(index: number, c: CipherView): string { + return c.id; + } +} diff --git a/apps/desktop/src/vault/app/vault/vault-v2.component.html b/apps/desktop/src/vault/app/vault/vault-v2.component.html new file mode 100644 index 00000000000..00e225f41d1 --- /dev/null +++ b/apps/desktop/src/vault/app/vault/vault-v2.component.html @@ -0,0 +1,79 @@ + + + + + + + + + + + + + + + {{ "attachments" | i18n }} + + {{ "premium" | i18n }} + + + + + + + + + + + + + + + + + + + + + + + diff --git a/apps/desktop/src/vault/app/vault/vault-v2.component.ts b/apps/desktop/src/vault/app/vault/vault-v2.component.ts new file mode 100644 index 00000000000..05c6c5e261e --- /dev/null +++ b/apps/desktop/src/vault/app/vault/vault-v2.component.ts @@ -0,0 +1,774 @@ +import { CommonModule } from "@angular/common"; +import { + ChangeDetectorRef, + Component, + NgZone, + OnDestroy, + OnInit, + ViewChild, + ViewContainerRef, +} from "@angular/core"; +import { takeUntilDestroyed } from "@angular/core/rxjs-interop"; +import { ActivatedRoute, Router } from "@angular/router"; +import { firstValueFrom, Subject, takeUntil, switchMap } from "rxjs"; +import { filter, map, take } from "rxjs/operators"; + +import { CollectionView } from "@bitwarden/admin-console/common"; +import { ModalRef } from "@bitwarden/angular/components/modal/modal.ref"; +import { ModalService } from "@bitwarden/angular/services/modal.service"; +import { VaultViewPasswordHistoryService } from "@bitwarden/angular/services/view-password-history.service"; +import { VaultFilter } from "@bitwarden/angular/vault/vault-filter/models/vault-filter.model"; +import { ApiService } from "@bitwarden/common/abstractions/api.service"; +import { EventCollectionService } from "@bitwarden/common/abstractions/event/event-collection.service"; +import { Account, 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"; +import { EventType } from "@bitwarden/common/enums"; +import { BroadcasterService } from "@bitwarden/common/platform/abstractions/broadcaster.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/platform/sync"; +import { CipherId, UserId } from "@bitwarden/common/types/guid"; +import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service"; +import { PremiumUpgradePromptService } from "@bitwarden/common/vault/abstractions/premium-upgrade-prompt.service"; +import { TotpService } from "@bitwarden/common/vault/abstractions/totp.service"; +import { ViewPasswordHistoryService } from "@bitwarden/common/vault/abstractions/view-password-history.service"; +import { CipherType } from "@bitwarden/common/vault/enums"; +import { CipherRepromptType } from "@bitwarden/common/vault/enums/cipher-reprompt-type"; +import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view"; +import { FolderView } from "@bitwarden/common/vault/models/view/folder.view"; +import { + BadgeModule, + ButtonModule, + DialogService, + ItemModule, + ToastService, +} from "@bitwarden/components"; +import { I18nPipe } from "@bitwarden/ui-common"; +import { + AttachmentDialogResult, + AttachmentsV2Component, + ChangeLoginPasswordService, + CipherFormConfig, + CipherFormConfigService, + CipherFormGenerationService, + CipherFormMode, + CipherFormModule, + CipherViewComponent, + DecryptionFailureDialogComponent, + DefaultChangeLoginPasswordService, + DefaultCipherFormConfigService, + PasswordRepromptService, +} from "@bitwarden/vault"; + +import { NavComponent } from "../../../app/layout/nav.component"; +import { SearchBarService } from "../../../app/layout/search/search-bar.service"; +import { DesktopCredentialGenerationService } from "../../../services/desktop-cipher-form-generator.service"; +import { DesktopPremiumUpgradePromptService } from "../../../services/desktop-premium-upgrade-prompt.service"; +import { invokeMenu, RendererMenuItem } from "../../../utils"; + +import { FolderAddEditComponent } from "./folder-add-edit.component"; +import { ItemFooterComponent } from "./item-footer.component"; +import { VaultFilterComponent } from "./vault-filter/vault-filter.component"; +import { VaultFilterModule } from "./vault-filter/vault-filter.module"; +import { VaultItemsV2Component } from "./vault-items-v2.component"; + +const BroadcasterSubscriptionId = "VaultComponent"; + +@Component({ + selector: "app-vault", + templateUrl: "vault-v2.component.html", + standalone: true, + imports: [ + BadgeModule, + CommonModule, + CipherFormModule, + CipherViewComponent, + ItemFooterComponent, + I18nPipe, + ItemModule, + ButtonModule, + NavComponent, + VaultFilterModule, + VaultItemsV2Component, + ], + providers: [ + { + provide: CipherFormConfigService, + useClass: DefaultCipherFormConfigService, + }, + { + provide: ChangeLoginPasswordService, + useClass: DefaultChangeLoginPasswordService, + }, + { + provide: ViewPasswordHistoryService, + useClass: VaultViewPasswordHistoryService, + }, + { + provide: PremiumUpgradePromptService, + useClass: DesktopPremiumUpgradePromptService, + }, + { provide: CipherFormGenerationService, useClass: DesktopCredentialGenerationService }, + ], +}) +export class VaultV2Component implements OnInit, OnDestroy { + @ViewChild(VaultItemsV2Component, { static: true }) + vaultItemsComponent: VaultItemsV2Component | null = null; + @ViewChild(VaultFilterComponent, { static: true }) + vaultFilterComponent: VaultFilterComponent | null = null; + @ViewChild("folderAddEdit", { read: ViewContainerRef, static: true }) + folderAddEditModalRef: ViewContainerRef | null = null; + + action: CipherFormMode | "view" | null = null; + cipherId: string | null = null; + favorites = false; + type: CipherType | null = null; + folderId: string | null = null; + collectionId: string | null = null; + organizationId: string | null = null; + myVaultOnly = false; + addType: CipherType | undefined = undefined; + addOrganizationId: string | null = null; + addCollectionIds: string[] | null = null; + showingModal = false; + deleted = false; + userHasPremiumAccess = false; + activeFilter: VaultFilter = new VaultFilter(); + activeUserId: UserId | null = null; + cipherRepromptId: string | null = null; + cipher: CipherView | null = new CipherView(); + collections: CollectionView[] | null = null; + config: CipherFormConfig | null = null; + + protected canAccessAttachments$ = this.accountService.activeAccount$.pipe( + filter((account): account is Account => !!account), + switchMap((account) => + this.billingAccountProfileStateService.hasPremiumFromAnySource$(account.id), + ), + ); + + private modal: ModalRef | null = null; + private componentIsDestroyed$ = new Subject(); + + constructor( + private route: ActivatedRoute, + private router: Router, + private i18nService: I18nService, + private modalService: ModalService, + private broadcasterService: BroadcasterService, + private changeDetectorRef: ChangeDetectorRef, + private ngZone: NgZone, + private syncService: SyncService, + private messagingService: MessagingService, + private platformUtilsService: PlatformUtilsService, + private eventCollectionService: EventCollectionService, + private totpService: TotpService, + private passwordRepromptService: PasswordRepromptService, + private searchBarService: SearchBarService, + private apiService: ApiService, + private dialogService: DialogService, + private billingAccountProfileStateService: BillingAccountProfileStateService, + private toastService: ToastService, + private accountService: AccountService, + private cipherService: CipherService, + private formConfigService: CipherFormConfigService, + private premiumUpgradePromptService: PremiumUpgradePromptService, + ) {} + + async ngOnInit() { + this.accountService.activeAccount$ + .pipe( + filter((account): account is Account => !!account), + switchMap((account) => + this.billingAccountProfileStateService.hasPremiumFromAnySource$(account.id), + ), + takeUntil(this.componentIsDestroyed$), + ) + .subscribe((canAccessPremium: boolean) => { + this.userHasPremiumAccess = canAccessPremium; + }); + + this.broadcasterService.subscribe(BroadcasterSubscriptionId, (message: any) => { + this.ngZone + .run(async () => { + let detectChanges = true; + try { + switch (message.command) { + case "newLogin": + await this.addCipher(CipherType.Login).catch(() => {}); + break; + case "newCard": + await this.addCipher(CipherType.Card).catch(() => {}); + break; + case "newIdentity": + await this.addCipher(CipherType.Identity).catch(() => {}); + break; + case "newSecureNote": + await this.addCipher(CipherType.SecureNote).catch(() => {}); + break; + case "newSshKey": + await this.addCipher(CipherType.SshKey).catch(() => {}); + break; + case "focusSearch": + (document.querySelector("#search") as HTMLInputElement)?.select(); + detectChanges = false; + break; + case "syncCompleted": + if (this.vaultItemsComponent) { + await this.vaultItemsComponent + .reload(this.activeFilter.buildFilter()) + .catch(() => {}); + } + if (this.vaultFilterComponent) { + await this.vaultFilterComponent + .reloadCollectionsAndFolders(this.activeFilter) + .catch(() => {}); + await this.vaultFilterComponent.reloadOrganizations().catch(() => {}); + } + break; + case "modalShown": + this.showingModal = true; + break; + case "modalClosed": + this.showingModal = false; + break; + case "copyUsername": { + if (this.cipher?.login?.username) { + this.copyValue(this.cipher, this.cipher?.login?.username, "username", "Username"); + } + break; + } + case "copyPassword": { + if (this.cipher?.login?.password && this.cipher.viewPassword) { + this.copyValue(this.cipher, this.cipher.login.password, "password", "Password"); + await this.eventCollectionService + .collect(EventType.Cipher_ClientCopiedPassword, this.cipher.id) + .catch(() => {}); + } + break; + } + case "copyTotp": { + if ( + this.cipher?.login?.hasTotp && + (this.cipher.organizationUseTotp || this.userHasPremiumAccess) + ) { + const value = await firstValueFrom( + this.totpService.getCode$(this.cipher.login.totp), + ).catch(() => null); + if (value) { + this.copyValue(this.cipher, value.code, "verificationCodeTotp", "TOTP"); + } + } + break; + } + default: + detectChanges = false; + break; + } + } catch { + // Ignore errors + } + if (detectChanges) { + this.changeDetectorRef.detectChanges(); + } + }) + .catch(() => {}); + }); + + if (!this.syncService.syncInProgress) { + await this.load().catch(() => {}); + } + + this.searchBarService.setEnabled(true); + this.searchBarService.setPlaceholderText(this.i18nService.t("searchVault")); + + const authRequest = await this.apiService.getLastAuthRequest().catch(() => null); + if (authRequest != null) { + this.messagingService.send("openLoginApproval", { + notificationId: authRequest.id, + }); + } + + this.activeUserId = await firstValueFrom( + this.accountService.activeAccount$.pipe(getUserId), + ).catch(() => null); + + if (this.activeUserId) { + this.cipherService + .failedToDecryptCiphers$(this.activeUserId) + .pipe( + map((ciphers) => ciphers?.filter((c) => !c.isDeleted) ?? []), + filter((ciphers) => ciphers.length > 0), + take(1), + takeUntil(this.componentIsDestroyed$), + ) + .subscribe((ciphers) => { + DecryptionFailureDialogComponent.open(this.dialogService, { + cipherIds: ciphers.map((c) => c.id as CipherId), + }); + }); + } + } + + ngOnDestroy() { + this.searchBarService.setEnabled(false); + this.broadcasterService.unsubscribe(BroadcasterSubscriptionId); + this.componentIsDestroyed$.next(true); + this.componentIsDestroyed$.complete(); + } + + async load() { + const params = await firstValueFrom(this.route.queryParams).catch(); + if (params.cipherId) { + const cipherView = new CipherView(); + cipherView.id = params.cipherId; + if (params.action === "clone") { + await this.cloneCipher(cipherView).catch(() => {}); + } else if (params.action === "edit") { + await this.editCipher(cipherView).catch(() => {}); + } else { + await this.viewCipher(cipherView).catch(() => {}); + } + } else if (params.action === "add") { + this.addType = Number(params.addType); + await this.addCipher(this.addType).catch(() => {}); + } + + this.activeFilter = new VaultFilter({ + status: params.deleted ? "trash" : params.favorites ? "favorites" : "all", + cipherType: + params.action === "add" || params.type == null + ? undefined + : (parseInt(params.type) as CipherType), + selectedFolderId: params.folderId, + selectedCollectionId: params.selectedCollectionId, + selectedOrganizationId: params.selectedOrganizationId, + myVaultOnly: params.myVaultOnly ?? false, + }); + if (this.vaultItemsComponent) { + await this.vaultItemsComponent.reload(this.activeFilter.buildFilter()).catch(() => {}); + } + } + + async viewCipher(cipher: CipherView) { + if (await this.shouldReprompt(cipher, "view")) { + return; + } + this.cipherId = cipher.id; + this.cipher = cipher; + this.collections = + this.vaultFilterComponent?.collections.fullList.filter((c) => + cipher.collectionIds.includes(c.id), + ) ?? null; + this.action = "view"; + await this.go().catch(() => {}); + } + + async openAttachmentsDialog() { + if (!this.userHasPremiumAccess) { + await this.premiumUpgradePromptService.promptForPremium(); + return; + } + const dialogRef = AttachmentsV2Component.open(this.dialogService, { + cipherId: this.cipherId as CipherId, + }); + const result = await firstValueFrom(dialogRef.closed).catch(() => null); + if ( + result?.action === AttachmentDialogResult.Removed || + result?.action === AttachmentDialogResult.Uploaded + ) { + await this.vaultItemsComponent?.refresh().catch(() => {}); + } + } + + viewCipherMenu(cipher: CipherView) { + const menu: RendererMenuItem[] = [ + { + label: this.i18nService.t("view"), + click: () => { + this.functionWithChangeDetection(() => { + this.viewCipher(cipher).catch(() => {}); + }); + }, + }, + ]; + + if (cipher.decryptionFailure) { + invokeMenu(menu); + return; + } + + if (!cipher.isDeleted) { + menu.push({ + label: this.i18nService.t("edit"), + click: () => { + this.functionWithChangeDetection(() => { + this.editCipher(cipher).catch(() => {}); + }); + }, + }); + if (!cipher.organizationId) { + menu.push({ + label: this.i18nService.t("clone"), + click: () => { + this.functionWithChangeDetection(() => { + this.cloneCipher(cipher).catch(() => {}); + }); + }, + }); + } + } + + switch (cipher.type) { + case CipherType.Login: + if ( + cipher.login.canLaunch || + cipher.login.username != null || + cipher.login.password != null + ) { + menu.push({ type: "separator" }); + } + if (cipher.login.canLaunch) { + menu.push({ + label: this.i18nService.t("launch"), + click: () => this.platformUtilsService.launchUri(cipher.login.launchUri), + }); + } + if (cipher.login.username != null) { + menu.push({ + label: this.i18nService.t("copyUsername"), + click: () => this.copyValue(cipher, cipher.login.username, "username", "Username"), + }); + } + if (cipher.login.password != null && cipher.viewPassword) { + menu.push({ + label: this.i18nService.t("copyPassword"), + click: () => { + this.copyValue(cipher, cipher.login.password, "password", "Password"); + this.eventCollectionService + .collect(EventType.Cipher_ClientCopiedPassword, cipher.id) + .catch(() => {}); + }, + }); + } + if (cipher.login.hasTotp && (cipher.organizationUseTotp || this.userHasPremiumAccess)) { + menu.push({ + label: this.i18nService.t("copyVerificationCodeTotp"), + click: async () => { + const value = await firstValueFrom( + this.totpService.getCode$(cipher.login.totp), + ).catch(() => null); + if (value) { + this.copyValue(cipher, value.code, "verificationCodeTotp", "TOTP"); + } + }, + }); + } + break; + case CipherType.Card: + if (cipher.card.number != null || cipher.card.code != null) { + menu.push({ type: "separator" }); + } + if (cipher.card.number != null) { + menu.push({ + label: this.i18nService.t("copyNumber"), + click: () => this.copyValue(cipher, cipher.card.number, "number", "Card Number"), + }); + } + if (cipher.card.code != null) { + menu.push({ + label: this.i18nService.t("copySecurityCode"), + click: () => { + this.copyValue(cipher, cipher.card.code, "securityCode", "Security Code"); + this.eventCollectionService + .collect(EventType.Cipher_ClientCopiedCardCode, cipher.id) + .catch(() => {}); + }, + }); + } + break; + default: + break; + } + invokeMenu(menu); + } + + async shouldReprompt(cipher: CipherView, action: "edit" | "clone" | "view"): Promise { + return !(await this.canNavigateAway(action, cipher)) || !(await this.passwordReprompt(cipher)); + } + + async buildFormConfig(action: CipherFormMode) { + this.config = await this.formConfigService + .buildConfig(action, this.cipherId as CipherId, this.addType) + .catch(() => null); + } + + async editCipher(cipher: CipherView) { + if (await this.shouldReprompt(cipher, "edit")) { + return; + } + this.cipherId = cipher.id; + this.cipher = cipher; + await this.buildFormConfig("edit"); + this.action = "edit"; + await this.go().catch(() => {}); + } + + async cloneCipher(cipher: CipherView) { + if (await this.shouldReprompt(cipher, "clone")) { + return; + } + this.cipherId = cipher.id; + this.cipher = cipher; + await this.buildFormConfig("clone"); + this.action = "clone"; + await this.go().catch(() => {}); + } + + async addCipher(type: CipherType) { + this.addType = type || this.activeFilter.cipherType; + this.cipherId = null; + await this.buildFormConfig("add"); + this.action = "add"; + this.prefillCipherFromFilter(); + await this.go().catch(() => {}); + + if (type === CipherType.SshKey) { + this.toastService.showToast({ + variant: "success", + title: "", + message: this.i18nService.t("sshKeyGenerated"), + }); + } + } + + async savedCipher(cipher: CipherView) { + this.cipherId = null; + this.action = "view"; + await this.vaultItemsComponent?.refresh().catch(() => {}); + this.cipherId = cipher.id; + this.cipher = cipher; + if (this.activeUserId) { + await this.cipherService.clearCache(this.activeUserId).catch(() => {}); + } + await this.vaultItemsComponent?.load(this.activeFilter.buildFilter()).catch(() => {}); + await this.go().catch(() => {}); + await this.vaultItemsComponent?.refresh().catch(() => {}); + } + + async deleteCipher() { + this.cipherId = null; + this.cipher = null; + this.action = null; + await this.go().catch(() => {}); + await this.vaultItemsComponent?.refresh().catch(() => {}); + } + + async restoreCipher() { + this.cipherId = null; + this.action = null; + await this.go().catch(() => {}); + await this.vaultItemsComponent?.refresh().catch(() => {}); + } + + async cancelCipher(cipher: CipherView) { + this.cipherId = cipher.id; + this.cipher = cipher; + this.action = this.cipherId != null ? "view" : null; + await this.go().catch(() => {}); + } + + async applyVaultFilter(vaultFilter: VaultFilter) { + this.searchBarService.setPlaceholderText( + this.i18nService.t(this.calculateSearchBarLocalizationString(vaultFilter)), + ); + this.activeFilter = vaultFilter; + await this.vaultItemsComponent + ?.reload(this.activeFilter.buildFilter(), vaultFilter.status === "trash") + .catch(() => {}); + await this.go().catch(() => {}); + } + + private calculateSearchBarLocalizationString(vaultFilter: VaultFilter): string { + if (vaultFilter.status === "favorites") { + return "searchFavorites"; + } + if (vaultFilter.status === "trash") { + return "searchTrash"; + } + if (vaultFilter.cipherType != null) { + return "searchType"; + } + if (vaultFilter.selectedFolderId != null && vaultFilter.selectedFolderId !== "none") { + return "searchFolder"; + } + if (vaultFilter.selectedCollectionId != null) { + return "searchCollection"; + } + if (vaultFilter.selectedOrganizationId != null) { + return "searchOrganization"; + } + if (vaultFilter.myVaultOnly) { + return "searchMyVault"; + } + return "searchVault"; + } + + async addFolder() { + this.messagingService.send("newFolder"); + } + + async editFolder(folderId: string) { + if (this.modal != null) { + this.modal.close(); + } + if (this.folderAddEditModalRef == null) { + return; + } + const [modal, childComponent] = await this.modalService + .openViewRef( + FolderAddEditComponent, + this.folderAddEditModalRef, + (comp) => (comp.folderId = folderId), + ) + .catch(() => [null, null] as any); + this.modal = modal; + if (childComponent) { + childComponent.onSavedFolder.subscribe(async (folder: FolderView) => { + this.modal?.close(); + await this.vaultFilterComponent + ?.reloadCollectionsAndFolders(this.activeFilter) + .catch(() => {}); + }); + childComponent.onDeletedFolder.subscribe(async (folder: FolderView) => { + this.modal?.close(); + await this.vaultFilterComponent + ?.reloadCollectionsAndFolders(this.activeFilter) + .catch(() => {}); + }); + } + if (this.modal) { + this.modal.onClosed.pipe(takeUntilDestroyed()).subscribe(() => { + this.modal = null; + }); + } + } + + private dirtyInput(): boolean { + return ( + (this.action === "add" || this.action === "edit" || this.action === "clone") && + document.querySelectorAll("vault-cipher-form .ng-dirty").length > 0 + ); + } + + private async wantsToSaveChanges(): Promise { + const confirmed = await this.dialogService + .openSimpleDialog({ + title: { key: "unsavedChangesTitle" }, + content: { key: "unsavedChangesConfirmation" }, + type: "warning", + }) + .catch(() => false); + return !confirmed; + } + + private async go(queryParams: any = null) { + if (queryParams == null) { + queryParams = { + action: this.action, + cipherId: this.cipherId, + favorites: this.favorites ? true : null, + type: this.type, + folderId: this.folderId, + collectionId: this.collectionId, + deleted: this.deleted ? true : null, + organizationId: this.organizationId, + myVaultOnly: this.myVaultOnly, + }; + } + this.router + .navigate([], { + relativeTo: this.route, + queryParams: queryParams, + replaceUrl: true, + }) + .catch(() => {}); + } + + private addCipherWithChangeDetection(type: CipherType) { + this.functionWithChangeDetection(() => this.addCipher(type).catch(() => {})); + } + + private copyValue(cipher: CipherView, value: string, labelI18nKey: string, aType: string) { + this.functionWithChangeDetection(() => { + (async () => { + if ( + cipher.reprompt !== CipherRepromptType.None && + this.passwordRepromptService.protectedFields().includes(aType) && + !(await this.passwordReprompt(cipher)) + ) { + return; + } + this.platformUtilsService.copyToClipboard(value); + this.toastService.showToast({ + variant: "info", + title: undefined, + message: this.i18nService.t("valueCopied", this.i18nService.t(labelI18nKey)), + }); + if (this.action === "view") { + this.messagingService.send("minimizeOnCopy"); + } + })().catch(() => {}); + }); + } + + private functionWithChangeDetection(func: () => void) { + this.ngZone.run(() => { + func(); + this.changeDetectorRef.detectChanges(); + }); + } + + private prefillCipherFromFilter() { + if (this.activeFilter.selectedCollectionId != null && this.vaultFilterComponent != null) { + const collections = this.vaultFilterComponent.collections.fullList.filter( + (c) => c.id === this.activeFilter.selectedCollectionId, + ); + if (collections.length > 0) { + this.addOrganizationId = collections[0].organizationId; + this.addCollectionIds = [this.activeFilter.selectedCollectionId]; + } + } else if (this.activeFilter.selectedOrganizationId) { + this.addOrganizationId = this.activeFilter.selectedOrganizationId; + } + if (this.activeFilter.selectedFolderId && this.activeFilter.selectedFolder) { + this.folderId = this.activeFilter.selectedFolderId; + } + } + + private async canNavigateAway(action: string, cipher?: CipherView) { + if (this.action === action && (!cipher || this.cipherId === cipher.id)) { + return false; + } else if (this.dirtyInput() && (await this.wantsToSaveChanges())) { + return false; + } + return true; + } + + private async passwordReprompt(cipher: CipherView) { + if (cipher.reprompt === CipherRepromptType.None) { + this.cipherRepromptId = null; + return true; + } + if (this.cipherRepromptId === cipher.id) { + return true; + } + const repromptResult = await this.passwordRepromptService.showPasswordPrompt(); + if (repromptResult) { + this.cipherRepromptId = cipher.id; + } + return repromptResult; + } +} diff --git a/apps/desktop/src/vault/app/vault/vault.component.ts b/apps/desktop/src/vault/app/vault/vault.component.ts index a21a285a428..6c0d5ef81d0 100644 --- a/apps/desktop/src/vault/app/vault/vault.component.ts +++ b/apps/desktop/src/vault/app/vault/vault.component.ts @@ -151,6 +151,9 @@ export class VaultComponent implements OnInit, OnDestroy { case "newSecureNote": await this.addCipher(CipherType.SecureNote); break; + case "newSshKey": + await this.addCipher(CipherType.SshKey); + break; case "focusSearch": (document.querySelector("#search") as HTMLInputElement).select(); detectChanges = false; @@ -470,6 +473,14 @@ export class VaultComponent implements OnInit, OnDestroy { this.cipherId = null; this.prefillNewCipherFromFilter(); this.go(); + + if (type === CipherType.SshKey) { + this.toastService.showToast({ + variant: "success", + title: "", + message: this.i18nService.t("sshKeyGenerated"), + }); + } } addCipherOptions() { diff --git a/apps/desktop/stores/chocolatey/bitwarden.nuspec b/apps/desktop/stores/chocolatey/bitwarden.nuspec index dc95703614d..450fa734736 100644 --- a/apps/desktop/stores/chocolatey/bitwarden.nuspec +++ b/apps/desktop/stores/chocolatey/bitwarden.nuspec @@ -10,7 +10,7 @@ Bitwarden Inc. https://bitwarden.com/ https://raw.githubusercontent.com/bitwarden/brand/master/icons/256x256.png - Copyright © 2015-2024 Bitwarden Inc. + Copyright © 2015-2025 Bitwarden Inc. https://github.com/bitwarden/clients/ https://bitwarden.com/help/ https://github.com/bitwarden/clients/issues diff --git a/apps/web/README.md b/apps/web/README.md index f43a9dc1614..c5e03eebb59 100644 --- a/apps/web/README.md +++ b/apps/web/README.md @@ -1,12 +1,12 @@ - + The Bitwarden web project is an Angular application that powers the web vault (https://vault.bitwarden.com/). - - + + diff --git a/apps/web/package.json b/apps/web/package.json index 1f4ae9c29cf..e65848602e9 100644 --- a/apps/web/package.json +++ b/apps/web/package.json @@ -1,6 +1,6 @@ { "name": "@bitwarden/web-vault", - "version": "2025.4.0", + "version": "2025.4.1", "scripts": { "build:oss": "cross-env NODE_OPTIONS=\"--max-old-space-size=8192\" webpack", "build:bit": "cross-env NODE_OPTIONS=\"--max-old-space-size=8192\" webpack -c ../../bitwarden_license/bit-web/webpack.config.js", diff --git a/apps/web/src/app/admin-console/organizations/collections/utils/collection-utils.spec.ts b/apps/web/src/app/admin-console/organizations/collections/utils/collection-utils.spec.ts index 0354a08c285..abd99d37355 100644 --- a/apps/web/src/app/admin-console/organizations/collections/utils/collection-utils.spec.ts +++ b/apps/web/src/app/admin-console/organizations/collections/utils/collection-utils.spec.ts @@ -1,6 +1,7 @@ import { CollectionView } from "@bitwarden/admin-console/common"; +import { TreeNode } from "@bitwarden/common/vault/models/domain/tree-node"; -import { getNestedCollectionTree } from "./collection-utils"; +import { getNestedCollectionTree, getFlatCollectionTree } from "./collection-utils"; describe("CollectionUtils Service", () => { describe("getNestedCollectionTree", () => { @@ -36,4 +37,63 @@ describe("CollectionUtils Service", () => { expect(result).toEqual([]); }); }); + + describe("getFlatCollectionTree", () => { + it("should flatten a tree node with no children", () => { + // Arrange + const collection = new CollectionView(); + collection.name = "Test Collection"; + collection.id = "test-id"; + + const treeNodes: TreeNode[] = [ + new TreeNode(collection, null), + ]; + + // Act + const result = getFlatCollectionTree(treeNodes); + + // Assert + expect(result.length).toBe(1); + expect(result[0]).toBe(collection); + }); + + it("should flatten a tree node with children", () => { + // Arrange + const parentCollection = new CollectionView(); + parentCollection.name = "Parent"; + parentCollection.id = "parent-id"; + + const child1Collection = new CollectionView(); + child1Collection.name = "Child 1"; + child1Collection.id = "child1-id"; + + const child2Collection = new CollectionView(); + child2Collection.name = "Child 2"; + child2Collection.id = "child2-id"; + + const grandchildCollection = new CollectionView(); + grandchildCollection.name = "Grandchild"; + grandchildCollection.id = "grandchild-id"; + + const parentNode = new TreeNode(parentCollection, null); + const child1Node = new TreeNode(child1Collection, parentNode); + const child2Node = new TreeNode(child2Collection, parentNode); + const grandchildNode = new TreeNode(grandchildCollection, child1Node); + + parentNode.children = [child1Node, child2Node]; + child1Node.children = [grandchildNode]; + + const treeNodes: TreeNode[] = [parentNode]; + + // Act + const result = getFlatCollectionTree(treeNodes); + + // Assert + expect(result.length).toBe(4); + expect(result[0]).toBe(parentCollection); + expect(result).toContain(child1Collection); + expect(result).toContain(child2Collection); + expect(result).toContain(grandchildCollection); + }); + }); }); diff --git a/apps/web/src/app/admin-console/organizations/collections/utils/collection-utils.ts b/apps/web/src/app/admin-console/organizations/collections/utils/collection-utils.ts index 2926ff3acee..95ae911bbf6 100644 --- a/apps/web/src/app/admin-console/organizations/collections/utils/collection-utils.ts +++ b/apps/web/src/app/admin-console/organizations/collections/utils/collection-utils.ts @@ -37,6 +37,27 @@ export function getNestedCollectionTree( return nodes; } +export function getFlatCollectionTree( + nodes: TreeNode[], +): CollectionAdminView[]; +export function getFlatCollectionTree(nodes: TreeNode[]): CollectionView[]; +export function getFlatCollectionTree( + nodes: TreeNode[], +): (CollectionView | CollectionAdminView)[] { + if (!nodes || nodes.length === 0) { + return []; + } + + return nodes.flatMap((node) => { + if (!node.children || node.children.length === 0) { + return [node.node]; + } + + const children = getFlatCollectionTree(node.children); + return [node.node, ...children]; + }); +} + function cloneCollection(collection: CollectionView): CollectionView; function cloneCollection(collection: CollectionAdminView): CollectionAdminView; function cloneCollection( diff --git a/apps/web/src/app/admin-console/organizations/collections/vault.component.html b/apps/web/src/app/admin-console/organizations/collections/vault.component.html index 604d326bf37..22da9a566f4 100644 --- a/apps/web/src/app/admin-console/organizations/collections/vault.component.html +++ b/apps/web/src/app/admin-console/organizations/collections/vault.component.html @@ -1,4 +1,15 @@ - + + + + + - + (false); protected currentSearchText$: Observable; - protected freeTrial$: Observable; - protected resellerWarning$: Observable; + protected useOrganizationWarningsService$: Observable; + protected freeTrialWhenWarningsServiceDisabled$: Observable; + protected resellerWarningWhenWarningsServiceDisabled$: Observable; protected prevCipherId: string | null = null; protected userId: UserId; /** @@ -257,6 +262,7 @@ export class VaultComponent implements OnInit, OnDestroy { private resellerWarningService: ResellerWarningService, private accountService: AccountService, private billingNotificationService: BillingNotificationService, + private organizationWarningsService: OrganizationWarningsService, ) {} async ngOnInit() { @@ -434,23 +440,33 @@ export class VaultComponent implements OnInit, OnDestroy { } this.showAddAccessToggle = false; - let collectionsToReturn = []; + let searchableCollectionNodes: TreeNode[] = []; if (filter.collectionId === undefined || filter.collectionId === All) { - collectionsToReturn = collections.map((c) => c.node); + searchableCollectionNodes = collections; } else { const selectedCollection = ServiceUtils.getTreeNodeObjectFromList( collections, filter.collectionId, ); - collectionsToReturn = selectedCollection?.children.map((c) => c.node) ?? []; + searchableCollectionNodes = selectedCollection?.children ?? []; } + let collectionsToReturn: CollectionAdminView[] = []; + if (await this.searchService.isSearchable(this.userId, searchText)) { + // Flatten the tree for searching through all levels + const flatCollectionTree: CollectionAdminView[] = + getFlatCollectionTree(searchableCollectionNodes); + collectionsToReturn = this.searchPipe.transform( - collectionsToReturn, + flatCollectionTree, searchText, - (collection: CollectionAdminView) => collection.name, - (collection: CollectionAdminView) => collection.id, + (collection) => collection.name, + (collection) => collection.id, + ); + } else { + collectionsToReturn = searchableCollectionNodes.map( + (treeNode: TreeNode): CollectionAdminView => treeNode.node, ); } @@ -620,9 +636,23 @@ export class VaultComponent implements OnInit, OnDestroy { ) .subscribe(); - this.unpaidSubscriptionDialog$.pipe(takeUntil(this.destroy$)).subscribe(); + // Billing Warnings + this.useOrganizationWarningsService$ = this.configService.getFeatureFlag$( + FeatureFlag.UseOrganizationWarningsService, + ); - this.freeTrial$ = combineLatest([ + this.useOrganizationWarningsService$ + .pipe( + switchMap((enabled) => + enabled + ? this.organizationWarningsService.showInactiveSubscriptionDialog$(this.organization) + : this.unpaidSubscriptionDialog$, + ), + takeUntil(this.destroy$), + ) + .subscribe(); + + const freeTrial$ = combineLatest([ organization$, this.hasSubscription$.pipe(filter((hasSubscription) => hasSubscription !== null)), ]).pipe( @@ -647,7 +677,12 @@ export class VaultComponent implements OnInit, OnDestroy { filter((result) => result !== null), ); - this.resellerWarning$ = organization$.pipe( + this.freeTrialWhenWarningsServiceDisabled$ = this.useOrganizationWarningsService$.pipe( + filter((enabled) => !enabled), + switchMap(() => freeTrial$), + ); + + const resellerWarning$ = organization$.pipe( filter((org) => org.isOwner), switchMap((org) => from(this.billingApiService.getOrganizationBillingMetadata(org.id)).pipe( @@ -657,6 +692,12 @@ export class VaultComponent implements OnInit, OnDestroy { map(({ org, metadata }) => this.resellerWarningService.getWarning(org, metadata)), ); + this.resellerWarningWhenWarningsServiceDisabled$ = this.useOrganizationWarningsService$.pipe( + filter((enabled) => !enabled), + switchMap(() => resellerWarning$), + ); + // End Billing Warnings + firstSetup$ .pipe( switchMap(() => this.refresh$), @@ -811,6 +852,7 @@ export class VaultComponent implements OnInit, OnDestroy { const dialogRef = AttachmentsV2Component.open(this.dialogService, { cipherId: cipher.id as CipherId, + organizationId: cipher.organizationId as OrganizationId, }); const result = await firstValueFrom(dialogRef.closed); 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 2079d592a28..02be3476ad5 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 @@ -63,7 +63,7 @@ {{ "upgradeEventLogMessage" | i18n }} @@ -125,10 +125,10 @@ - {{ "limitedEventLogs" | i18n: ProductTierType[organization?.productTierType] }} + {{ "upgradeEventLogTitleMessage" | i18n }} - {{ "upgradeForFullEvents" | i18n }} + {{ "upgradeForFullEventsMessage" | i18n }} diff --git a/apps/web/src/app/auth/core/services/rotateable-key-set.service.spec.ts b/apps/web/src/app/auth/core/services/rotateable-key-set.service.spec.ts index 1a83fed37b7..8579c4c1dc8 100644 --- a/apps/web/src/app/auth/core/services/rotateable-key-set.service.spec.ts +++ b/apps/web/src/app/auth/core/services/rotateable-key-set.service.spec.ts @@ -36,7 +36,7 @@ describe("RotateableKeySetService", () => { keyService.makeKeyPair.mockResolvedValue(["publicKey", encryptedPrivateKey as any]); keyService.getUserKey.mockResolvedValue({ key: userKey.key } as any); encryptService.encapsulateKeyUnsigned.mockResolvedValue(encryptedUserKey as any); - encryptService.encrypt.mockResolvedValue(encryptedPublicKey as any); + encryptService.wrapEncapsulationKey.mockResolvedValue(encryptedPublicKey as any); const result = await service.createKeySet(externalKey as any); diff --git a/apps/web/src/app/auth/core/services/rotateable-key-set.service.ts b/apps/web/src/app/auth/core/services/rotateable-key-set.service.ts index 8510aa1c29a..ef78e09e6b9 100644 --- a/apps/web/src/app/auth/core/services/rotateable-key-set.service.ts +++ b/apps/web/src/app/auth/core/services/rotateable-key-set.service.ts @@ -29,7 +29,10 @@ export class RotateableKeySetService { userKey, rawPublicKey, ); - const encryptedPublicKey = await this.encryptService.encrypt(rawPublicKey, userKey); + const encryptedPublicKey = await this.encryptService.wrapEncapsulationKey( + rawPublicKey, + userKey, + ); return new RotateableKeySet(encryptedUserKey, encryptedPublicKey, encryptedPrivateKey); } @@ -62,7 +65,10 @@ export class RotateableKeySetService { if (publicKey == null) { throw new Error("failed to rotate key set: could not decrypt public key"); } - const newEncryptedPublicKey = await this.encryptService.encrypt(publicKey, newUserKey); + const newEncryptedPublicKey = await this.encryptService.wrapEncapsulationKey( + publicKey, + newUserKey, + ); const newEncryptedUserKey = await this.encryptService.encapsulateKeyUnsigned( newUserKey, publicKey, diff --git a/apps/web/src/app/auth/organization-invite/accept-organization.service.spec.ts b/apps/web/src/app/auth/organization-invite/accept-organization.service.spec.ts index cc2d0e371ff..0a30aa16478 100644 --- a/apps/web/src/app/auth/organization-invite/accept-organization.service.spec.ts +++ b/apps/web/src/app/auth/organization-invite/accept-organization.service.spec.ts @@ -24,7 +24,6 @@ import { OrgKey } from "@bitwarden/common/types/key"; import { DialogService } from "@bitwarden/components"; import { KeyService } from "@bitwarden/key-management"; -import { OrganizationTrustComponent } from "../../admin-console/organizations/manage/organization-trust.component"; import { I18nService } from "../../core/i18n.service"; import { @@ -93,6 +92,9 @@ describe("AcceptOrganizationInviteService", () => { "orgPublicKey", { encryptedString: "string" } as EncString, ]); + encryptService.wrapDecapsulationKey.mockResolvedValue({ + encryptedString: "string", + } as EncString); encryptService.encrypt.mockResolvedValue({ encryptedString: "string" } as EncString); const invite = createOrgInvite({ initOrganization: true }); @@ -200,11 +202,6 @@ describe("AcceptOrganizationInviteService", () => { encryptedString: "encryptedString", } as EncString); - jest.mock("../../admin-console/organizations/manage/organization-trust.component"); - OrganizationTrustComponent.open = jest.fn().mockReturnValue({ - closed: new BehaviorSubject(true), - }); - await globalState.update(() => invite); policyService.getResetPasswordPolicyOptions.mockReturnValue([ @@ -217,7 +214,6 @@ describe("AcceptOrganizationInviteService", () => { const result = await sut.validateAndAcceptInvite(invite); expect(result).toBe(true); - expect(OrganizationTrustComponent.open).toHaveBeenCalled(); expect(encryptService.encapsulateKeyUnsigned).toHaveBeenCalledWith( { key: "userKey" }, Utils.fromB64ToArray("publicKey"), diff --git a/apps/web/src/app/auth/organization-invite/accept-organization.service.ts b/apps/web/src/app/auth/organization-invite/accept-organization.service.ts index 8b5db9f4872..b6a7719c548 100644 --- a/apps/web/src/app/auth/organization-invite/accept-organization.service.ts +++ b/apps/web/src/app/auth/organization-invite/accept-organization.service.ts @@ -31,8 +31,6 @@ import { OrgKey } from "@bitwarden/common/types/key"; import { DialogService } from "@bitwarden/components"; import { KeyService } from "@bitwarden/key-management"; -import { OrganizationTrustComponent } from "../../admin-console/organizations/manage/organization-trust.component"; - import { OrganizationInvite } from "./organization-invite"; // We're storing the organization invite for 2 reasons: @@ -189,15 +187,6 @@ export class AcceptOrganizationInviteService { } const publicKey = Utils.fromB64ToArray(response.publicKey); - const dialogRef = OrganizationTrustComponent.open(this.dialogService, { - name: invite.organizationName, - orgId: invite.organizationId, - publicKey, - }); - const result = await firstValueFrom(dialogRef.closed); - if (result !== true) { - throw new Error("Organization not trusted, aborting user key rotation"); - } const activeUserId = (await firstValueFrom(this.accountService.activeAccount$)).id; const userKey = await firstValueFrom(this.keyService.userKey$(activeUserId)); diff --git a/apps/web/src/app/auth/settings/change-password.component.ts b/apps/web/src/app/auth/settings/change-password.component.ts index d8e371fd36b..ffa5247ad08 100644 --- a/apps/web/src/app/auth/settings/change-password.component.ts +++ b/apps/web/src/app/auth/settings/change-password.component.ts @@ -310,13 +310,16 @@ export class ChangePasswordComponent newMasterKey: MasterKey, newUserKey: [UserKey, EncString], ) { - const masterKey = await this.keyService.makeMasterKey( - this.currentMasterPassword, - await firstValueFrom(this.accountService.activeAccount$.pipe(map((a) => a?.email))), - await this.kdfConfigService.getKdfConfig(), + const [userId, email] = await firstValueFrom( + this.accountService.activeAccount$.pipe(map((a) => [a?.id, a?.email])), + ); + + const masterKey = await this.keyService.makeMasterKey( + this.currentMasterPassword, + email, + await this.kdfConfigService.getKdfConfig(userId), ); - const userId = await firstValueFrom(getUserId(this.accountService.activeAccount$)); const newLocalKeyHash = await this.keyService.hashMasterKey( this.masterPassword, newMasterKey, diff --git a/apps/web/src/app/auth/settings/emergency-access/view/emergency-view-dialog.component.spec.ts b/apps/web/src/app/auth/settings/emergency-access/view/emergency-view-dialog.component.spec.ts index 3ffb54f5b1c..b341fc4f8e4 100644 --- a/apps/web/src/app/auth/settings/emergency-access/view/emergency-view-dialog.component.spec.ts +++ b/apps/web/src/app/auth/settings/emergency-access/view/emergency-view-dialog.component.spec.ts @@ -8,14 +8,16 @@ import { OrganizationService } from "@bitwarden/common/admin-console/abstraction import { AccountService } from "@bitwarden/common/auth/abstractions/account.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 { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; import { Utils } from "@bitwarden/common/platform/misc/utils"; import { FakeAccountService, mockAccountServiceWith } from "@bitwarden/common/spec"; -import { UserId } from "@bitwarden/common/types/guid"; +import { UserId, EmergencyAccessId } from "@bitwarden/common/types/guid"; import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service"; import { FolderService } from "@bitwarden/common/vault/abstractions/folder/folder.service.abstraction"; import { CipherType } from "@bitwarden/common/vault/enums"; import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view"; +import { LoginView } from "@bitwarden/common/vault/models/view/login.view"; import { TaskService } from "@bitwarden/common/vault/tasks"; import { DialogService, DialogRef, DIALOG_DATA } from "@bitwarden/components"; import { ChangeLoginPasswordService } from "@bitwarden/vault"; @@ -28,14 +30,15 @@ describe("EmergencyViewDialogComponent", () => { const open = jest.fn(); const close = jest.fn(); + const emergencyAccessId = "emergency-access-id" as EmergencyAccessId; const mockCipher = { id: "cipher1", name: "Cipher", type: CipherType.Login, - login: { uris: [] }, + login: { uris: [] } as Partial, card: {}, - } as CipherView; + } as Partial as CipherView; const accountService: FakeAccountService = mockAccountServiceWith(Utils.newGuid() as UserId); @@ -56,6 +59,7 @@ describe("EmergencyViewDialogComponent", () => { { provide: DIALOG_DATA, useValue: { cipher: mockCipher } }, { provide: AccountService, useValue: accountService }, { provide: TaskService, useValue: mock() }, + { provide: LogService, useValue: mock() }, ], }) .overrideComponent(EmergencyViewDialogComponent, { @@ -94,18 +98,24 @@ describe("EmergencyViewDialogComponent", () => { }); it("opens dialog", () => { - EmergencyViewDialogComponent.open({ open } as unknown as DialogService, { cipher: mockCipher }); + EmergencyViewDialogComponent.open({ open } as unknown as DialogService, { + cipher: mockCipher, + emergencyAccessId, + }); expect(open).toHaveBeenCalled(); }); it("closes the dialog", () => { - EmergencyViewDialogComponent.open({ open } as unknown as DialogService, { cipher: mockCipher }); + EmergencyViewDialogComponent.open({ open } as unknown as DialogService, { + cipher: mockCipher, + emergencyAccessId, + }); fixture.detectChanges(); const cancelButton = fixture.debugElement.queryAll(By.css("button")).pop(); - cancelButton.nativeElement.click(); + cancelButton!.nativeElement.click(); expect(close).toHaveBeenCalled(); }); diff --git a/apps/web/src/app/auth/settings/emergency-access/view/emergency-view-dialog.component.ts b/apps/web/src/app/auth/settings/emergency-access/view/emergency-view-dialog.component.ts index bcae11c3264..0022da7f3a9 100644 --- a/apps/web/src/app/auth/settings/emergency-access/view/emergency-view-dialog.component.ts +++ b/apps/web/src/app/auth/settings/emergency-access/view/emergency-view-dialog.component.ts @@ -2,6 +2,7 @@ import { CommonModule } from "@angular/common"; import { Component, Inject } from "@angular/core"; import { JslibModule } from "@bitwarden/angular/jslib.module"; +import { VaultViewPasswordHistoryService } from "@bitwarden/angular/services/view-password-history.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { EmergencyAccessId } from "@bitwarden/common/types/guid"; import { PremiumUpgradePromptService } from "@bitwarden/common/vault/abstractions/premium-upgrade-prompt.service"; @@ -21,8 +22,6 @@ import { DefaultChangeLoginPasswordService, } from "@bitwarden/vault"; -import { WebViewPasswordHistoryService } from "../../../../vault/services/web-view-password-history.service"; - export interface EmergencyViewDialogParams { /** The cipher being viewed. */ cipher: CipherView; @@ -42,7 +41,7 @@ class PremiumUpgradePromptNoop implements PremiumUpgradePromptService { standalone: true, imports: [ButtonModule, CipherViewComponent, DialogModule, CommonModule, JslibModule], providers: [ - { provide: ViewPasswordHistoryService, useClass: WebViewPasswordHistoryService }, + { provide: ViewPasswordHistoryService, useClass: VaultViewPasswordHistoryService }, { provide: PremiumUpgradePromptService, useClass: PremiumUpgradePromptNoop }, { provide: ChangeLoginPasswordService, useClass: DefaultChangeLoginPasswordService }, ], diff --git a/apps/web/src/app/auth/settings/security/change-kdf/change-kdf.component.ts b/apps/web/src/app/auth/settings/security/change-kdf/change-kdf.component.ts index 3c392795ef4..cbbef0e016b 100644 --- a/apps/web/src/app/auth/settings/security/change-kdf/change-kdf.component.ts +++ b/apps/web/src/app/auth/settings/security/change-kdf/change-kdf.component.ts @@ -2,8 +2,10 @@ // @ts-strict-ignore import { Component, OnDestroy, OnInit } from "@angular/core"; import { FormBuilder, FormControl, ValidatorFn, Validators } from "@angular/forms"; -import { Subject, takeUntil } from "rxjs"; +import { Subject, firstValueFrom, takeUntil } from "rxjs"; +import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; +import { getUserId } from "@bitwarden/common/auth/services/account.service"; import { DialogService } from "@bitwarden/components"; import { KdfConfigService, @@ -43,6 +45,7 @@ export class ChangeKdfComponent implements OnInit, OnDestroy { constructor( private dialogService: DialogService, private kdfConfigService: KdfConfigService, + private accountService: AccountService, private formBuilder: FormBuilder, ) { this.kdfOptions = [ @@ -52,7 +55,8 @@ export class ChangeKdfComponent implements OnInit, OnDestroy { } async ngOnInit() { - this.kdfConfig = await this.kdfConfigService.getKdfConfig(); + const userId = await firstValueFrom(getUserId(this.accountService.activeAccount$)); + this.kdfConfig = await this.kdfConfigService.getKdfConfig(userId); this.formGroup.get("kdf").setValue(this.kdfConfig.kdfType); this.setFormControlValues(this.kdfConfig); diff --git a/apps/web/src/app/billing/members/add-sponsorship-dialog.component.html b/apps/web/src/app/billing/members/add-sponsorship-dialog.component.html index 2dbcc577e54..405211d6ecb 100644 --- a/apps/web/src/app/billing/members/add-sponsorship-dialog.component.html +++ b/apps/web/src/app/billing/members/add-sponsorship-dialog.component.html @@ -34,7 +34,15 @@ - + {{ "save" | i18n }} diff --git a/apps/web/src/app/billing/members/add-sponsorship-dialog.component.ts b/apps/web/src/app/billing/members/add-sponsorship-dialog.component.ts index 54d9ae90009..1b044e66257 100644 --- a/apps/web/src/app/billing/members/add-sponsorship-dialog.component.ts +++ b/apps/web/src/app/billing/members/add-sponsorship-dialog.component.ts @@ -1,5 +1,5 @@ -import { DialogRef } from "@angular/cdk/dialog"; -import { Component } from "@angular/core"; +import { DIALOG_DATA, DialogConfig, DialogRef } from "@angular/cdk/dialog"; +import { Component, Inject } from "@angular/core"; import { AbstractControl, FormBuilder, @@ -10,32 +10,30 @@ import { ValidationErrors, Validators, } from "@angular/forms"; -import { firstValueFrom, map } from "rxjs"; +import { OrganizationUserApiService } from "@bitwarden/admin-console/common"; import { JslibModule } from "@bitwarden/angular/jslib.module"; -import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; +import { ApiService } from "@bitwarden/common/abstractions/api.service"; +import { PlanSponsorshipType } from "@bitwarden/common/billing/enums"; +import { EncryptService } from "@bitwarden/common/key-management/crypto/abstractions/encrypt.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; -import { ButtonModule, DialogModule, DialogService, FormFieldModule } from "@bitwarden/components"; +import { OrgKey } from "@bitwarden/common/types/key"; +import { + ButtonModule, + DialogModule, + DialogService, + FormFieldModule, + ToastService, +} from "@bitwarden/components"; interface RequestSponsorshipForm { sponsorshipEmail: FormControl; sponsorshipNote: FormControl; } -export interface AddSponsorshipDialogResult { - action: AddSponsorshipDialogAction; - value: Partial | null; -} - -interface AddSponsorshipFormValue { - sponsorshipEmail: string; - sponsorshipNote: string; - status: string; -} - -enum AddSponsorshipDialogAction { - Saved = "saved", - Canceled = "canceled", +interface AddSponsorshipDialogParams { + organizationId: string; + organizationKey: OrgKey; } @Component({ @@ -53,54 +51,82 @@ enum AddSponsorshipDialogAction { export class AddSponsorshipDialogComponent { sponsorshipForm: FormGroup; loading = false; + organizationId: string; + organizationKey: OrgKey; constructor( - private dialogRef: DialogRef, + private dialogRef: DialogRef, private formBuilder: FormBuilder, - private accountService: AccountService, private i18nService: I18nService, + private organizationUserApiService: OrganizationUserApiService, + private toastService: ToastService, + private apiService: ApiService, + private encryptService: EncryptService, + + @Inject(DIALOG_DATA) protected dialogParams: AddSponsorshipDialogParams, ) { + this.organizationId = this.dialogParams?.organizationId; + this.organizationKey = this.dialogParams.organizationKey; + this.sponsorshipForm = this.formBuilder.group({ sponsorshipEmail: new FormControl("", { validators: [Validators.email, Validators.required], - asyncValidators: [this.validateNotCurrentUserEmail.bind(this)], + asyncValidators: [this.isOrganizationMember.bind(this)], updateOn: "change", }), sponsorshipNote: new FormControl("", {}), }); } - static open(dialogService: DialogService): DialogRef { - return dialogService.open(AddSponsorshipDialogComponent); + static open(dialogService: DialogService, config: DialogConfig) { + return dialogService.open(AddSponsorshipDialogComponent, { + ...config, + data: config.data, + } as unknown as DialogConfig); } protected async save() { if (this.sponsorshipForm.invalid) { return; } - this.loading = true; - // TODO: This is a mockup implementation - needs to be updated with actual API integration - await new Promise((resolve) => setTimeout(resolve, 1000)); // Simulate API call - const formValue = this.sponsorshipForm.getRawValue(); - const dialogValue: Partial = { - status: "Sent", - sponsorshipEmail: formValue.sponsorshipEmail ?? "", - sponsorshipNote: formValue.sponsorshipNote ?? "", - }; + try { + const notes = this.sponsorshipForm.value.sponsorshipNote || ""; + const email = this.sponsorshipForm.value.sponsorshipEmail || ""; - this.dialogRef.close({ - action: AddSponsorshipDialogAction.Saved, - value: dialogValue, - }); + const encryptedNotes = await this.encryptService.encryptString(notes, this.organizationKey); + const isAdminInitiated = true; + await this.apiService.postCreateSponsorship(this.organizationId, { + sponsoredEmail: email, + planSponsorshipType: PlanSponsorshipType.FamiliesForEnterprise, + friendlyName: email, + isAdminInitiated, + notes: encryptedNotes.encryptedString, + }); + + this.toastService.showToast({ + variant: "success", + title: undefined, + message: this.i18nService.t("sponsorshipCreated"), + }); + await this.resetForm(); + } catch (e: any) { + this.toastService.showToast({ + variant: "error", + title: this.i18nService.t("errorOccurred"), + message: e?.message || this.i18nService.t("unexpectedError"), + }); + } this.loading = false; + + this.dialogRef.close(); } - protected close = () => { - this.dialogRef.close({ action: AddSponsorshipDialogAction.Canceled, value: null }); - }; + private async resetForm() { + this.sponsorshipForm.reset(); + } get sponsorshipEmailControl() { return this.sponsorshipForm.controls.sponsorshipEmail; @@ -110,24 +136,21 @@ export class AddSponsorshipDialogComponent { return this.sponsorshipForm.controls.sponsorshipNote; } - private async validateNotCurrentUserEmail( - control: AbstractControl, - ): Promise { + private async isOrganizationMember(control: AbstractControl): Promise { const value = control.value; - if (!value) { - return null; - } - const currentUserEmail = await firstValueFrom( - this.accountService.activeAccount$.pipe(map((a) => a?.email ?? "")), + const users = await this.organizationUserApiService.getAllMiniUserDetails(this.organizationId); + + const userExists = users.data.some( + (member) => member.email.toLowerCase() === value.toLowerCase(), ); - if (!currentUserEmail) { - return null; - } - - if (value.toLowerCase() === currentUserEmail.toLowerCase()) { - return { currentUserEmail: true }; + if (userExists) { + return { + isOrganizationMember: { + message: this.i18nService.t("organizationHasMemberMessage", value), + }, + }; } return null; diff --git a/apps/web/src/app/billing/members/free-bitwarden-families.component.html b/apps/web/src/app/billing/members/free-bitwarden-families.component.html index fe1dd15ab15..8eeef6f0f36 100644 --- a/apps/web/src/app/billing/members/free-bitwarden-families.component.html +++ b/apps/web/src/app/billing/members/free-bitwarden-families.component.html @@ -5,19 +5,95 @@ - - - - + + + + {{ "sponsorshipFreeBitwardenFamilies" | i18n }} + + + {{ "sponsoredFamiliesIncludeMessage" | i18n }}: + + {{ "sponsoredFamiliesPremiumAccess" | i18n }} + {{ "sponsoredFamiliesSharedCollectionsForFamilyMembers" | i18n }} + + - - - - + {{ "sponsoredBitwardenFamilies" | i18n }} -{{ "sponsoredFamiliesRemoveActiveSponsorship" | i18n }} + @if (loading()) { + + + {{ "loading" | i18n }} + + } + + @if (!loading() && sponsoredFamilies?.length > 0) { + + + + + {{ "recipient" | i18n }} + {{ "status" | i18n }} + {{ "notes" | i18n }} + + + + + @for (o of sponsoredFamilies; let i = $index; track i) { + + + {{ o.friendlyName }} + {{ o.statusMessage }} + {{ o.notes }} + + + + + + {{ "resendInvitation" | i18n }} + + + + + + + {{ "remove" | i18n }} + + + + + + } + + + + + } @else if (!loading()) { + + + {{ "noSponsoredFamiliesMessage" | i18n }} + {{ "nosponsoredFamiliesDetails" | i18n }} + + } + + @if (!loading() && sponsoredFamilies.length > 0) { + {{ "sponsoredFamiliesRemoveActiveSponsorship" | i18n }} + } + + diff --git a/apps/web/src/app/billing/members/free-bitwarden-families.component.ts b/apps/web/src/app/billing/members/free-bitwarden-families.component.ts index af43e5a4bc1..c141eaebd78 100644 --- a/apps/web/src/app/billing/members/free-bitwarden-families.component.ts +++ b/apps/web/src/app/billing/members/free-bitwarden-families.component.ts @@ -1,62 +1,259 @@ import { DialogRef } from "@angular/cdk/dialog"; -import { Component, OnInit } from "@angular/core"; -import { Router } from "@angular/router"; -import { firstValueFrom } from "rxjs"; +import { formatDate } from "@angular/common"; +import { Component, OnInit, signal } from "@angular/core"; +import { takeUntilDestroyed } from "@angular/core/rxjs-interop"; +import { ActivatedRoute } from "@angular/router"; +import { firstValueFrom, map, Observable, switchMap } from "rxjs"; -import { DialogService } from "@bitwarden/components"; +import { ApiService } from "@bitwarden/common/abstractions/api.service"; +import { OrganizationSponsorshipApiServiceAbstraction } from "@bitwarden/common/billing/abstractions/organizations/organization-sponsorship-api.service.abstraction"; +import { OrganizationSponsorshipInvitesResponse } from "@bitwarden/common/billing/models/response/organization-sponsorship-invites.response"; +import { EncryptService } from "@bitwarden/common/key-management/crypto/abstractions/encrypt.service"; +import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; +import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; +import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; +import { EncString } from "@bitwarden/common/platform/models/domain/enc-string"; +import { StateProvider } from "@bitwarden/common/platform/state"; +import { OrganizationId, UserId } from "@bitwarden/common/types/guid"; +import { OrgKey } from "@bitwarden/common/types/key"; +import { DialogService, ToastService } from "@bitwarden/components"; +import { KeyService } from "@bitwarden/key-management"; -import { FreeFamiliesPolicyService } from "../services/free-families-policy.service"; - -import { - AddSponsorshipDialogComponent, - AddSponsorshipDialogResult, -} from "./add-sponsorship-dialog.component"; -import { SponsoredFamily } from "./types/sponsored-family"; +import { AddSponsorshipDialogComponent } from "./add-sponsorship-dialog.component"; @Component({ selector: "app-free-bitwarden-families", templateUrl: "free-bitwarden-families.component.html", }) export class FreeBitwardenFamiliesComponent implements OnInit { + loading = signal(true); tabIndex = 0; - sponsoredFamilies: SponsoredFamily[] = []; + sponsoredFamilies: OrganizationSponsorshipInvitesResponse[] = []; + + organizationId = ""; + organizationKey$: Observable; + + private locale: string = ""; constructor( - private router: Router, + private route: ActivatedRoute, private dialogService: DialogService, - private freeFamiliesPolicyService: FreeFamiliesPolicyService, - ) {} + private apiService: ApiService, + private encryptService: EncryptService, + private keyService: KeyService, + private platformUtilsService: PlatformUtilsService, + private i18nService: I18nService, + private logService: LogService, + private toastService: ToastService, + private organizationSponsorshipApiService: OrganizationSponsorshipApiServiceAbstraction, + private stateProvider: StateProvider, + ) { + this.organizationId = this.route.snapshot.params.organizationId || ""; + this.organizationKey$ = this.stateProvider.activeUserId$.pipe( + switchMap( + (userId) => + this.keyService.orgKeys$(userId as UserId) as Observable>, + ), + map((organizationKeysById) => organizationKeysById[this.organizationId as OrganizationId]), + takeUntilDestroyed(), + ); + } async ngOnInit() { - await this.preventAccessToFreeFamiliesPage(); + this.locale = await firstValueFrom(this.i18nService.locale$); + await this.loadSponsorships(); + + this.loading.set(false); + } + + async loadSponsorships() { + if (!this.organizationId) { + return; + } + + const [response, orgKey] = await Promise.all([ + this.organizationSponsorshipApiService.getOrganizationSponsorship(this.organizationId), + firstValueFrom(this.organizationKey$), + ]); + + if (!orgKey) { + this.logService.error("Organization key not found"); + return; + } + + const organizationFamilies = response.data; + + this.sponsoredFamilies = await Promise.all( + organizationFamilies.map(async (family) => { + let decryptedNote = ""; + try { + decryptedNote = await this.encryptService.decryptString( + new EncString(family.notes), + orgKey, + ); + } catch (e) { + this.logService.error(e); + } + + const { statusMessage, statusClass } = this.getStatus( + this.isSelfHosted, + family.toDelete, + family.validUntil, + family.lastSyncDate, + this.locale, + ); + + const newFamily = { + ...family, + notes: decryptedNote, + statusMessage: statusMessage || "", + statusClass: statusClass || "tw-text-success", + status: statusMessage || "", + }; + + return new OrganizationSponsorshipInvitesResponse(newFamily); + }), + ); } async addSponsorship() { - const addSponsorshipDialogRef: DialogRef = - AddSponsorshipDialogComponent.open(this.dialogService); + const addSponsorshipDialogRef: DialogRef = AddSponsorshipDialogComponent.open( + this.dialogService, + { + data: { + organizationId: this.organizationId, + organizationKey: await firstValueFrom(this.organizationKey$), + }, + }, + ); - const dialogRef = await firstValueFrom(addSponsorshipDialogRef.closed); + await firstValueFrom(addSponsorshipDialogRef.closed); - if (dialogRef?.value) { - this.sponsoredFamilies = [dialogRef.value, ...this.sponsoredFamilies]; + await this.loadSponsorships(); + } + + async removeSponsorship(sponsorship: OrganizationSponsorshipInvitesResponse) { + try { + await this.doRevokeSponsorship(sponsorship); + } catch (e) { + this.logService.error(e); } } - removeSponsorhip(sponsorship: any) { - const index = this.sponsoredFamilies.findIndex( - (e) => e.sponsorshipEmail == sponsorship.sponsorshipEmail, - ); - this.sponsoredFamilies.splice(index, 1); + get isSelfHosted(): boolean { + return this.platformUtilsService.isSelfHost(); } - private async preventAccessToFreeFamiliesPage() { - const showFreeFamiliesPage = await firstValueFrom( - this.freeFamiliesPolicyService.showFreeFamilies$, - ); + async resendEmail(sponsorship: OrganizationSponsorshipInvitesResponse) { + await this.apiService.postResendSponsorshipOffer(sponsorship.sponsoringOrganizationUserId); + this.toastService.showToast({ + variant: "success", + title: undefined, + message: this.i18nService.t("emailSent"), + }); + } - if (!showFreeFamiliesPage) { - await this.router.navigate(["/"]); + private async doRevokeSponsorship(sponsorship: OrganizationSponsorshipInvitesResponse) { + const content = sponsorship.validUntil + ? this.i18nService.t( + "updatedRevokeSponsorshipConfirmationForAcceptedSponsorship", + sponsorship.friendlyName, + formatDate(sponsorship.validUntil, "MM/dd/yyyy", this.locale), + ) + : this.i18nService.t( + "updatedRevokeSponsorshipConfirmationForSentSponsorship", + sponsorship.friendlyName, + ); + + const confirmed = await this.dialogService.openSimpleDialog({ + title: `${this.i18nService.t("removeSponsorship")}?`, + content, + acceptButtonText: { key: "remove" }, + type: "warning", + }); + + if (!confirmed) { return; } + + await this.apiService.deleteRevokeSponsorship(sponsorship.sponsoringOrganizationUserId); + + this.toastService.showToast({ + variant: "success", + title: undefined, + message: this.i18nService.t("reclaimedFreePlan"), + }); + + await this.loadSponsorships(); + } + + private getStatus( + selfHosted: boolean, + toDelete?: boolean, + validUntil?: Date, + lastSyncDate?: Date, + locale: string = "", + ): { statusMessage: string; statusClass: "tw-text-success" | "tw-text-danger" } { + /* + * Possible Statuses: + * Requested (self-hosted only) + * Sent + * Active + * RequestRevoke + * RevokeWhenExpired + */ + + if (toDelete && validUntil) { + // They want to delete but there is a valid until date which means there is an active sponsorship + return { + statusMessage: this.i18nService.t( + "revokeWhenExpired", + formatDate(validUntil, "MM/dd/yyyy", locale), + ), + statusClass: "tw-text-danger", + }; + } + + if (toDelete) { + // They want to delete and we don't have a valid until date so we can + // this should only happen on a self-hosted install + return { + statusMessage: this.i18nService.t("requestRemoved"), + statusClass: "tw-text-danger", + }; + } + + if (validUntil) { + // They don't want to delete and they have a valid until date + // that means they are actively sponsoring someone + return { + statusMessage: this.i18nService.t("active"), + statusClass: "tw-text-success", + }; + } + + if (selfHosted && lastSyncDate) { + // We are on a self-hosted install and it has been synced but we have not gotten + // a valid until date so we can't know if they are actively sponsoring someone + return { + statusMessage: this.i18nService.t("sent"), + statusClass: "tw-text-success", + }; + } + + if (!selfHosted) { + // We are in cloud and all other status checks have been false therefore we have + // sent the request but it hasn't been accepted yet + return { + statusMessage: this.i18nService.t("sent"), + statusClass: "tw-text-success", + }; + } + + // We are on a self-hosted install and we have not synced yet + return { + statusMessage: this.i18nService.t("requested"), + statusClass: "tw-text-success", + }; } } diff --git a/apps/web/src/app/billing/members/organization-member-families.component.html b/apps/web/src/app/billing/members/organization-member-families.component.html deleted file mode 100644 index c5b7283d9d9..00000000000 --- a/apps/web/src/app/billing/members/organization-member-families.component.html +++ /dev/null @@ -1,47 +0,0 @@ - - - - {{ "membersWithSponsoredFamilies" | i18n }} - - - {{ "memberFamilies" | i18n }} - - @if (loading) { - - - {{ "loading" | i18n }} - - } - - @if (!loading && memberFamilies?.length > 0) { - - - - - {{ "member" | i18n }} - {{ "status" | i18n }} - - - - - @for (o of memberFamilies; let i = $index; track i) { - - - {{ o.sponsorshipEmail }} - {{ o.status }} - - - } - - - - - } @else { - - - {{ "noMemberFamilies" | i18n }} - {{ "noMemberFamiliesDescription" | i18n }} - - } - - diff --git a/apps/web/src/app/billing/members/organization-member-families.component.ts b/apps/web/src/app/billing/members/organization-member-families.component.ts deleted file mode 100644 index 52c95646a11..00000000000 --- a/apps/web/src/app/billing/members/organization-member-families.component.ts +++ /dev/null @@ -1,34 +0,0 @@ -import { Component, Input, OnDestroy, OnInit } from "@angular/core"; -import { Subject } from "rxjs"; - -import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; - -import { SponsoredFamily } from "./types/sponsored-family"; - -@Component({ - selector: "app-organization-member-families", - templateUrl: "organization-member-families.component.html", -}) -export class OrganizationMemberFamiliesComponent implements OnInit, OnDestroy { - tabIndex = 0; - loading = false; - - @Input() memberFamilies: SponsoredFamily[] = []; - - private _destroy = new Subject(); - - constructor(private platformUtilsService: PlatformUtilsService) {} - - async ngOnInit() { - this.loading = false; - } - - ngOnDestroy(): void { - this._destroy.next(); - this._destroy.complete(); - } - - get isSelfHosted(): boolean { - return this.platformUtilsService.isSelfHost(); - } -} diff --git a/apps/web/src/app/billing/members/organization-sponsored-families.component.html b/apps/web/src/app/billing/members/organization-sponsored-families.component.html deleted file mode 100644 index 7db96deb4ab..00000000000 --- a/apps/web/src/app/billing/members/organization-sponsored-families.component.html +++ /dev/null @@ -1,87 +0,0 @@ - - - - {{ "sponsorFreeBitwardenFamilies" | i18n }} - - - {{ "sponsoredFamiliesInclude" | i18n }}: - - {{ "sponsoredFamiliesPremiumAccess" | i18n }} - {{ "sponsoredFamiliesSharedCollections" | i18n }} - - - - {{ "sponsoredBitwardenFamilies" | i18n }} - - @if (loading) { - - - {{ "loading" | i18n }} - - } - - @if (!loading && sponsoredFamilies?.length > 0) { - - - - - {{ "recipient" | i18n }} - {{ "status" | i18n }} - {{ "notes" | i18n }} - - - - - @for (o of sponsoredFamilies; let i = $index; track i) { - - - {{ o.sponsorshipEmail }} - {{ o.status }} - {{ o.sponsorshipNote }} - - - - - - {{ "resendInvitation" | i18n }} - - - - - - - {{ "remove" | i18n }} - - - - - - } - - - - - } @else { - - - {{ "noSponsoredFamilies" | i18n }} - {{ "noSponsoredFamiliesDescription" | i18n }} - - } - - diff --git a/apps/web/src/app/billing/members/organization-sponsored-families.component.ts b/apps/web/src/app/billing/members/organization-sponsored-families.component.ts deleted file mode 100644 index 7cc46634a38..00000000000 --- a/apps/web/src/app/billing/members/organization-sponsored-families.component.ts +++ /dev/null @@ -1,39 +0,0 @@ -import { Component, EventEmitter, Input, OnDestroy, OnInit, Output } from "@angular/core"; -import { Subject } from "rxjs"; - -import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; - -import { SponsoredFamily } from "./types/sponsored-family"; - -@Component({ - selector: "app-organization-sponsored-families", - templateUrl: "organization-sponsored-families.component.html", -}) -export class OrganizationSponsoredFamiliesComponent implements OnInit, OnDestroy { - loading = false; - tabIndex = 0; - - @Input() sponsoredFamilies: SponsoredFamily[] = []; - @Output() removeSponsorshipEvent = new EventEmitter(); - - private _destroy = new Subject(); - - constructor(private platformUtilsService: PlatformUtilsService) {} - - async ngOnInit() { - this.loading = false; - } - - get isSelfHosted(): boolean { - return this.platformUtilsService.isSelfHost(); - } - - remove(sponsorship: SponsoredFamily) { - this.removeSponsorshipEvent.emit(sponsorship); - } - - ngOnDestroy(): void { - this._destroy.next(); - this._destroy.complete(); - } -} diff --git a/apps/web/src/app/billing/organizations/change-plan-dialog.component.html b/apps/web/src/app/billing/organizations/change-plan-dialog.component.html index 64a694cdef0..91e2e83a92c 100644 --- a/apps/web/src/app/billing/organizations/change-plan-dialog.component.html +++ b/apps/web/src/app/billing/organizations/change-plan-dialog.component.html @@ -150,10 +150,11 @@ >{{ "costPerMember" | i18n - : ((selectableProduct.isAnnual - ? selectableProduct.PasswordManager.seatPrice / 12 - : selectableProduct.PasswordManager.seatPrice - ) + : (((this.sub.useSecretsManager + ? selectableProduct.SecretsManager.seatPrice + : 0) + + selectableProduct.PasswordManager.seatPrice) / + (selectableProduct.isAnnual ? 12 : 1) | currency: "$") }} @@ -983,7 +984,7 @@ - {{ "upgrade" | i18n }} + {{ submitButtonLabel }} {{ "cancel" | i18n }} diff --git a/apps/web/src/app/billing/organizations/change-plan-dialog.component.ts b/apps/web/src/app/billing/organizations/change-plan-dialog.component.ts index 589b96463a1..8cff90edd5b 100644 --- a/apps/web/src/app/billing/organizations/change-plan-dialog.component.ts +++ b/apps/web/src/app/billing/organizations/change-plan-dialog.component.ts @@ -12,7 +12,7 @@ import { } from "@angular/core"; import { FormBuilder, Validators } from "@angular/forms"; import { Router } from "@angular/router"; -import { Subject, firstValueFrom, map, switchMap, takeUntil } from "rxjs"; +import { firstValueFrom, map, Subject, switchMap, takeUntil } from "rxjs"; import { ManageTaxInformationComponent } from "@bitwarden/angular/billing/components"; import { ApiService } from "@bitwarden/common/abstractions/api.service"; @@ -31,10 +31,10 @@ import { getUserId } from "@bitwarden/common/auth/services/account.service"; import { BillingApiServiceAbstraction, BillingInformation, + OrganizationBillingServiceAbstraction as OrganizationBillingService, OrganizationInformation, PaymentInformation, PlanInformation, - OrganizationBillingServiceAbstraction as OrganizationBillingService, } from "@bitwarden/common/billing/abstractions"; import { TaxServiceAbstraction } from "@bitwarden/common/billing/abstractions/tax.service.abstraction"; import { @@ -744,7 +744,7 @@ export class ChangePlanDialogComponent implements OnInit, OnDestroy { const doSubmit = async (): Promise => { let orgId: string = null; - if (this.isSubscriptionCanceled) { + if (this.sub?.subscription?.status === "canceled") { await this.restartSubscription(); orgId = this.organizationId; } else { @@ -1089,4 +1089,15 @@ export class ChangePlanDialogComponent implements OnInit, OnDestroy { this.isSubscriptionCanceled ); } + + get submitButtonLabel(): string { + if ( + this.organization.productTierType !== ProductTierType.Free && + this.sub.subscription.status === "canceled" + ) { + return this.i18nService.t("restart"); + } else { + return this.i18nService.t("upgrade"); + } + } } 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 59f8dd34c37..e373b0d4dee 100644 --- a/apps/web/src/app/billing/organizations/organization-plans.component.ts +++ b/apps/web/src/app/billing/organizations/organization-plans.component.ts @@ -812,7 +812,7 @@ export class OrganizationPlansComponent implements OnInit, OnDestroy { ); const providerKey = await this.keyService.getProviderKey(this.providerId); providerRequest.organizationCreateRequest.key = ( - await this.encryptService.encrypt(orgKey.key, providerKey) + await this.encryptService.wrapSymmetricKey(orgKey, providerKey) ).encryptedString; const orgId = ( await this.apiService.postProviderCreateOrganization(this.providerId, providerRequest) diff --git a/apps/web/src/app/billing/organizations/organization-subscription-cloud.component.ts b/apps/web/src/app/billing/organizations/organization-subscription-cloud.component.ts index f20d447b093..5cbaf940695 100644 --- a/apps/web/src/app/billing/organizations/organization-subscription-cloud.component.ts +++ b/apps/web/src/app/billing/organizations/organization-subscription-cloud.component.ts @@ -494,7 +494,11 @@ export class OrganizationSubscriptionCloudComponent implements OnInit, OnDestroy }; get showChangePlanButton() { - return this.sub.plan.productTier !== ProductTierType.Enterprise && !this.showChangePlan; + return ( + !this.showChangePlan && + this.sub.plan.productTier !== ProductTierType.Enterprise && + !this.sub.subscription?.cancelled + ); } get canUseBillingSync() { diff --git a/apps/web/src/app/billing/services/organization-warnings.service.spec.ts b/apps/web/src/app/billing/services/organization-warnings.service.spec.ts new file mode 100644 index 00000000000..88c571e2d67 --- /dev/null +++ b/apps/web/src/app/billing/services/organization-warnings.service.spec.ts @@ -0,0 +1,356 @@ +import { Router } from "@angular/router"; +import { mock, MockProxy } from "jest-mock-extended"; +import { firstValueFrom, lastValueFrom } from "rxjs"; + +import { OrganizationApiServiceAbstraction } from "@bitwarden/common/admin-console/abstractions/organization/organization-api.service.abstraction"; +import { Organization } from "@bitwarden/common/admin-console/models/domain/organization"; +import { OrganizationBillingApiServiceAbstraction } from "@bitwarden/common/billing/abstractions/organizations/organization-billing-api.service.abstraction"; +import { OrganizationWarningsResponse } from "@bitwarden/common/billing/models/response/organization-warnings.response"; +import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; +import { DialogService, SimpleDialogOptions } from "@bitwarden/components"; + +import { OrganizationWarningsService } from "./organization-warnings.service"; + +describe("OrganizationWarningsService", () => { + let dialogService: MockProxy; + let i18nService: MockProxy; + let organizationApiService: MockProxy; + let organizationBillingApiService: MockProxy; + let router: MockProxy; + + let organizationWarningsService: OrganizationWarningsService; + + const respond = (responseBody: any) => + Promise.resolve(new OrganizationWarningsResponse(responseBody)); + + const empty = () => Promise.resolve(new OrganizationWarningsResponse({})); + + beforeEach(() => { + dialogService = mock(); + i18nService = mock(); + organizationApiService = mock(); + organizationBillingApiService = mock(); + router = mock(); + + organizationWarningsService = new OrganizationWarningsService( + dialogService, + i18nService, + organizationApiService, + organizationBillingApiService, + router, + ); + }); + + describe("cache$", () => { + it("should only request warnings once for a specific organization and replay the cached result for multiple subscriptions", async () => { + const response1 = respond({ + freeTrial: { + remainingTrialDays: 1, + }, + }); + + const organization1 = { + id: "1", + name: "Test", + } as Organization; + + const response2 = respond({ + freeTrial: { + remainingTrialDays: 2, + }, + }); + + const organization2 = { + id: "2", + name: "Test", + } as Organization; + + organizationBillingApiService.getWarnings.mockImplementation((id) => { + if (id === organization1.id) { + return response1; + } + + if (id === organization2.id) { + return response2; + } + + return empty(); + }); + + const oneDayRemainingTranslation = "oneDayRemaining"; + const twoDaysRemainingTranslation = "twoDaysRemaining"; + + i18nService.t.mockImplementation((id, p1) => { + if (id === "freeTrialEndPromptTomorrowNoOrgName") { + return oneDayRemainingTranslation; + } + + if (id === "freeTrialEndPromptCount" && p1 === 2) { + return twoDaysRemainingTranslation; + } + + return ""; + }); + + const organization1Subscription1 = await firstValueFrom( + organizationWarningsService.getFreeTrialWarning$(organization1), + ); + + const organization1Subscription2 = await firstValueFrom( + organizationWarningsService.getFreeTrialWarning$(organization1), + ); + + expect(organization1Subscription1).toEqual({ + organization: organization1, + message: oneDayRemainingTranslation, + }); + + expect(organization1Subscription2).toEqual(organization1Subscription1); + + const organization2Subscription1 = await firstValueFrom( + organizationWarningsService.getFreeTrialWarning$(organization2), + ); + + const organization2Subscription2 = await firstValueFrom( + organizationWarningsService.getFreeTrialWarning$(organization2), + ); + + expect(organization2Subscription1).toEqual({ + organization: organization2, + message: twoDaysRemainingTranslation, + }); + + expect(organization2Subscription2).toEqual(organization2Subscription1); + + expect(organizationBillingApiService.getWarnings).toHaveBeenCalledTimes(2); + }); + }); + + describe("getFreeTrialWarning$", () => { + it("should not emit a free trial warning when none is included in the warnings response", (done) => { + const organization = { + id: "1", + name: "Test", + } as Organization; + + organizationBillingApiService.getWarnings.mockReturnValue(empty()); + + const warning$ = organizationWarningsService.getFreeTrialWarning$(organization); + + warning$.subscribe({ + next: () => { + fail("Observable should not emit a value."); + }, + complete: () => { + done(); + }, + }); + }); + + it("should emit a free trial warning when one is included in the warnings response", async () => { + const response = respond({ + freeTrial: { + remainingTrialDays: 1, + }, + }); + + const organization = { + id: "1", + name: "Test", + } as Organization; + + organizationBillingApiService.getWarnings.mockImplementation((id) => { + if (id === organization.id) { + return response; + } else { + return empty(); + } + }); + + const translation = "translation"; + i18nService.t.mockImplementation((id) => { + if (id === "freeTrialEndPromptTomorrowNoOrgName") { + return translation; + } else { + return ""; + } + }); + + const warning = await firstValueFrom( + organizationWarningsService.getFreeTrialWarning$(organization), + ); + + expect(warning).toEqual({ + organization, + message: translation, + }); + }); + }); + + describe("getResellerRenewalWarning$", () => { + it("should not emit a reseller renewal warning when none is included in the warnings response", (done) => { + const organization = { + id: "1", + name: "Test", + } as Organization; + + organizationBillingApiService.getWarnings.mockReturnValue(empty()); + + const warning$ = organizationWarningsService.getResellerRenewalWarning$(organization); + + warning$.subscribe({ + next: () => { + fail("Observable should not emit a value."); + }, + complete: () => { + done(); + }, + }); + }); + + it("should emit a reseller renewal warning when one is included in the warnings response", async () => { + const response = respond({ + resellerRenewal: { + type: "upcoming", + upcoming: { + renewalDate: "2026-01-01T00:00:00.000Z", + }, + }, + }); + + const organization = { + id: "1", + name: "Test", + providerName: "Provider", + } as Organization; + + organizationBillingApiService.getWarnings.mockImplementation((id) => { + if (id === organization.id) { + return response; + } else { + return empty(); + } + }); + + const formattedDate = new Date("2026-01-01T00:00:00.000Z").toLocaleDateString("en-US", { + month: "short", + day: "2-digit", + year: "numeric", + }); + + const translation = "translation"; + i18nService.t.mockImplementation((id, p1, p2) => { + if ( + id === "resellerRenewalWarningMsg" && + p1 === organization.providerName && + p2 === formattedDate + ) { + return translation; + } else { + return ""; + } + }); + + const warning = await firstValueFrom( + organizationWarningsService.getResellerRenewalWarning$(organization), + ); + + expect(warning).toEqual({ + type: "info", + message: translation, + }); + }); + }); + + describe("showInactiveSubscriptionDialog$", () => { + it("should not emit the opening of a dialog for an inactive subscription warning when the warning is not included in the warnings response", (done) => { + const organization = { + id: "1", + name: "Test", + } as Organization; + + organizationBillingApiService.getWarnings.mockReturnValue(empty()); + + const warning$ = organizationWarningsService.showInactiveSubscriptionDialog$(organization); + + warning$.subscribe({ + next: () => { + fail("Observable should not emit a value."); + }, + complete: () => { + done(); + }, + }); + }); + + it("should emit the opening of a dialog for an inactive subscription warning when the warning is included in the warnings response", async () => { + const response = respond({ + inactiveSubscription: { + resolution: "add_payment_method", + }, + }); + + const organization = { + id: "1", + name: "Test", + providerName: "Provider", + } as Organization; + + organizationBillingApiService.getWarnings.mockImplementation((id) => { + if (id === organization.id) { + return response; + } else { + return empty(); + } + }); + + const titleTranslation = "title"; + const continueTranslation = "continue"; + const closeTranslation = "close"; + + i18nService.t.mockImplementation((id, param) => { + if (id === "suspendedOrganizationTitle" && param === organization.name) { + return titleTranslation; + } + if (id === "continue") { + return continueTranslation; + } + if (id === "close") { + return closeTranslation; + } + return ""; + }); + + const expectedOptions = { + title: titleTranslation, + content: { + key: "suspendedOwnerOrgMessage", + }, + type: "danger", + acceptButtonText: continueTranslation, + cancelButtonText: closeTranslation, + } as SimpleDialogOptions; + + dialogService.openSimpleDialog.mockImplementation((options) => { + if (JSON.stringify(options) == JSON.stringify(expectedOptions)) { + return Promise.resolve(true); + } else { + return Promise.resolve(false); + } + }); + + const observable$ = organizationWarningsService.showInactiveSubscriptionDialog$(organization); + + const routerNavigateSpy = jest.spyOn(router, "navigate").mockResolvedValue(true); + + await lastValueFrom(observable$); + + expect(routerNavigateSpy).toHaveBeenCalledWith( + ["organizations", `${organization.id}`, "billing", "payment-method"], + { + state: { launchPaymentModalAutomatically: true }, + }, + ); + }); + }); +}); diff --git a/apps/web/src/app/billing/services/organization-warnings.service.ts b/apps/web/src/app/billing/services/organization-warnings.service.ts new file mode 100644 index 00000000000..f75220a7744 --- /dev/null +++ b/apps/web/src/app/billing/services/organization-warnings.service.ts @@ -0,0 +1,201 @@ +import { Injectable } from "@angular/core"; +import { Router } from "@angular/router"; +import { + filter, + from, + lastValueFrom, + map, + Observable, + shareReplay, + switchMap, + takeWhile, +} from "rxjs"; +import { take } from "rxjs/operators"; + +import { OrganizationApiServiceAbstraction } from "@bitwarden/common/admin-console/abstractions/organization/organization-api.service.abstraction"; +import { Organization } from "@bitwarden/common/admin-console/models/domain/organization"; +import { OrganizationBillingApiServiceAbstraction } from "@bitwarden/common/billing/abstractions/organizations/organization-billing-api.service.abstraction"; +import { OrganizationWarningsResponse } from "@bitwarden/common/billing/models/response/organization-warnings.response"; +import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; +import { OrganizationId } from "@bitwarden/common/types/guid"; +import { DialogService } from "@bitwarden/components"; +import { openChangePlanDialog } from "@bitwarden/web-vault/app/billing/organizations/change-plan-dialog.component"; + +const format = (date: Date) => + date.toLocaleDateString("en-US", { + month: "short", + day: "2-digit", + year: "numeric", + }); + +export type FreeTrialWarning = { + organization: Pick; + message: string; +}; + +export type ResellerRenewalWarning = { + type: "info" | "warning"; + message: string; +}; + +@Injectable({ providedIn: "root" }) +export class OrganizationWarningsService { + private cache$ = new Map>(); + + constructor( + private dialogService: DialogService, + private i18nService: I18nService, + private organizationApiService: OrganizationApiServiceAbstraction, + private organizationBillingApiService: OrganizationBillingApiServiceAbstraction, + private router: Router, + ) {} + + getFreeTrialWarning$ = (organization: Organization): Observable => + this.getWarning$(organization, (response) => response.freeTrial).pipe( + map((warning) => { + const { remainingTrialDays } = warning; + + if (remainingTrialDays >= 2) { + return { + organization, + message: this.i18nService.t("freeTrialEndPromptCount", remainingTrialDays), + }; + } + + if (remainingTrialDays == 1) { + return { + organization, + message: this.i18nService.t("freeTrialEndPromptTomorrowNoOrgName"), + }; + } + + return { + organization, + message: this.i18nService.t("freeTrialEndingTodayWithoutOrgName"), + }; + }), + ); + + getResellerRenewalWarning$ = (organization: Organization): Observable => + this.getWarning$(organization, (response) => response.resellerRenewal).pipe( + map((warning): ResellerRenewalWarning | null => { + switch (warning.type) { + case "upcoming": { + return { + type: "info", + message: this.i18nService.t( + "resellerRenewalWarningMsg", + organization.providerName, + format(warning.upcoming!.renewalDate), + ), + }; + } + case "issued": { + return { + type: "info", + message: this.i18nService.t( + "resellerOpenInvoiceWarningMgs", + organization.providerName, + format(warning.issued!.issuedDate), + format(warning.issued!.dueDate), + ), + }; + } + case "past_due": { + return { + type: "warning", + message: this.i18nService.t( + "resellerPastDueWarningMsg", + organization.providerName, + format(warning.pastDue!.suspensionDate), + ), + }; + } + } + }), + filter((result): result is NonNullable => result !== null), + ); + + showInactiveSubscriptionDialog$ = (organization: Organization): Observable => + this.getWarning$(organization, (response) => response.inactiveSubscription).pipe( + switchMap(async (warning) => { + switch (warning.resolution) { + case "contact_provider": { + await this.dialogService.openSimpleDialog({ + title: this.i18nService.t("suspendedOrganizationTitle", organization.name), + content: { + key: "suspendedManagedOrgMessage", + placeholders: [organization.providerName], + }, + type: "danger", + acceptButtonText: this.i18nService.t("close"), + cancelButtonText: null, + }); + break; + } + case "add_payment_method": { + const confirmed = await this.dialogService.openSimpleDialog({ + title: this.i18nService.t("suspendedOrganizationTitle", organization.name), + content: { key: "suspendedOwnerOrgMessage" }, + type: "danger", + acceptButtonText: this.i18nService.t("continue"), + cancelButtonText: this.i18nService.t("close"), + }); + if (confirmed) { + await this.router.navigate( + ["organizations", `${organization.id}`, "billing", "payment-method"], + { + state: { launchPaymentModalAutomatically: true }, + }, + ); + } + break; + } + case "resubscribe": { + const subscription = await this.organizationApiService.getSubscription(organization.id); + const dialogReference = openChangePlanDialog(this.dialogService, { + data: { + organizationId: organization.id, + subscription: subscription, + productTierType: organization.productTierType, + }, + }); + await lastValueFrom(dialogReference.closed); + break; + } + case "contact_owner": { + await this.dialogService.openSimpleDialog({ + title: this.i18nService.t("suspendedOrganizationTitle", organization.name), + content: { key: "suspendedUserOrgMessage" }, + type: "danger", + acceptButtonText: this.i18nService.t("close"), + cancelButtonText: null, + }); + break; + } + } + }), + ); + + private getResponse$ = (organization: Organization): Observable => { + const existing = this.cache$.get(organization.id as OrganizationId); + if (existing) { + return existing; + } + const response$ = from(this.organizationBillingApiService.getWarnings(organization.id)).pipe( + shareReplay({ bufferSize: 1, refCount: false }), + ); + this.cache$.set(organization.id as OrganizationId, response$); + return response$; + }; + + private getWarning$ = ( + organization: Organization, + extract: (response: OrganizationWarningsResponse) => T | null | undefined, + ): Observable => + this.getResponse$(organization).pipe( + map(extract), + takeWhile((warning): warning is T => !!warning), + take(1), + ); +} diff --git a/apps/web/src/app/billing/settings/sponsored-families.component.html b/apps/web/src/app/billing/settings/sponsored-families.component.html index 12e942aaf18..7708f63365e 100644 --- a/apps/web/src/app/billing/settings/sponsored-families.component.html +++ b/apps/web/src/app/billing/settings/sponsored-families.component.html @@ -10,10 +10,10 @@ {{ "sponsoredFamiliesEligible" | i18n }}
{{ "settingsVaultOptions" | i18n }}
{{ "emptyVaultDescription" | i18n }}
{{ "noItemsInList" | i18n }}
+ {{ "attachments" | i18n }} + + {{ "premium" | i18n }} + +
- +
The Bitwarden web project is an Angular application that powers the web vault (https://vault.bitwarden.com/).
- - + + diff --git a/apps/web/package.json b/apps/web/package.json index 1f4ae9c29cf..e65848602e9 100644 --- a/apps/web/package.json +++ b/apps/web/package.json @@ -1,6 +1,6 @@ { "name": "@bitwarden/web-vault", - "version": "2025.4.0", + "version": "2025.4.1", "scripts": { "build:oss": "cross-env NODE_OPTIONS=\"--max-old-space-size=8192\" webpack", "build:bit": "cross-env NODE_OPTIONS=\"--max-old-space-size=8192\" webpack -c ../../bitwarden_license/bit-web/webpack.config.js", diff --git a/apps/web/src/app/admin-console/organizations/collections/utils/collection-utils.spec.ts b/apps/web/src/app/admin-console/organizations/collections/utils/collection-utils.spec.ts index 0354a08c285..abd99d37355 100644 --- a/apps/web/src/app/admin-console/organizations/collections/utils/collection-utils.spec.ts +++ b/apps/web/src/app/admin-console/organizations/collections/utils/collection-utils.spec.ts @@ -1,6 +1,7 @@ import { CollectionView } from "@bitwarden/admin-console/common"; +import { TreeNode } from "@bitwarden/common/vault/models/domain/tree-node"; -import { getNestedCollectionTree } from "./collection-utils"; +import { getNestedCollectionTree, getFlatCollectionTree } from "./collection-utils"; describe("CollectionUtils Service", () => { describe("getNestedCollectionTree", () => { @@ -36,4 +37,63 @@ describe("CollectionUtils Service", () => { expect(result).toEqual([]); }); }); + + describe("getFlatCollectionTree", () => { + it("should flatten a tree node with no children", () => { + // Arrange + const collection = new CollectionView(); + collection.name = "Test Collection"; + collection.id = "test-id"; + + const treeNodes: TreeNode[] = [ + new TreeNode(collection, null), + ]; + + // Act + const result = getFlatCollectionTree(treeNodes); + + // Assert + expect(result.length).toBe(1); + expect(result[0]).toBe(collection); + }); + + it("should flatten a tree node with children", () => { + // Arrange + const parentCollection = new CollectionView(); + parentCollection.name = "Parent"; + parentCollection.id = "parent-id"; + + const child1Collection = new CollectionView(); + child1Collection.name = "Child 1"; + child1Collection.id = "child1-id"; + + const child2Collection = new CollectionView(); + child2Collection.name = "Child 2"; + child2Collection.id = "child2-id"; + + const grandchildCollection = new CollectionView(); + grandchildCollection.name = "Grandchild"; + grandchildCollection.id = "grandchild-id"; + + const parentNode = new TreeNode(parentCollection, null); + const child1Node = new TreeNode(child1Collection, parentNode); + const child2Node = new TreeNode(child2Collection, parentNode); + const grandchildNode = new TreeNode(grandchildCollection, child1Node); + + parentNode.children = [child1Node, child2Node]; + child1Node.children = [grandchildNode]; + + const treeNodes: TreeNode[] = [parentNode]; + + // Act + const result = getFlatCollectionTree(treeNodes); + + // Assert + expect(result.length).toBe(4); + expect(result[0]).toBe(parentCollection); + expect(result).toContain(child1Collection); + expect(result).toContain(child2Collection); + expect(result).toContain(grandchildCollection); + }); + }); }); diff --git a/apps/web/src/app/admin-console/organizations/collections/utils/collection-utils.ts b/apps/web/src/app/admin-console/organizations/collections/utils/collection-utils.ts index 2926ff3acee..95ae911bbf6 100644 --- a/apps/web/src/app/admin-console/organizations/collections/utils/collection-utils.ts +++ b/apps/web/src/app/admin-console/organizations/collections/utils/collection-utils.ts @@ -37,6 +37,27 @@ export function getNestedCollectionTree( return nodes; } +export function getFlatCollectionTree( + nodes: TreeNode[], +): CollectionAdminView[]; +export function getFlatCollectionTree(nodes: TreeNode[]): CollectionView[]; +export function getFlatCollectionTree( + nodes: TreeNode[], +): (CollectionView | CollectionAdminView)[] { + if (!nodes || nodes.length === 0) { + return []; + } + + return nodes.flatMap((node) => { + if (!node.children || node.children.length === 0) { + return [node.node]; + } + + const children = getFlatCollectionTree(node.children); + return [node.node, ...children]; + }); +} + function cloneCollection(collection: CollectionView): CollectionView; function cloneCollection(collection: CollectionAdminView): CollectionAdminView; function cloneCollection( diff --git a/apps/web/src/app/admin-console/organizations/collections/vault.component.html b/apps/web/src/app/admin-console/organizations/collections/vault.component.html index 604d326bf37..22da9a566f4 100644 --- a/apps/web/src/app/admin-console/organizations/collections/vault.component.html +++ b/apps/web/src/app/admin-console/organizations/collections/vault.component.html @@ -1,4 +1,15 @@ - + + + + + - + (false); protected currentSearchText$: Observable; - protected freeTrial$: Observable; - protected resellerWarning$: Observable; + protected useOrganizationWarningsService$: Observable; + protected freeTrialWhenWarningsServiceDisabled$: Observable; + protected resellerWarningWhenWarningsServiceDisabled$: Observable; protected prevCipherId: string | null = null; protected userId: UserId; /** @@ -257,6 +262,7 @@ export class VaultComponent implements OnInit, OnDestroy { private resellerWarningService: ResellerWarningService, private accountService: AccountService, private billingNotificationService: BillingNotificationService, + private organizationWarningsService: OrganizationWarningsService, ) {} async ngOnInit() { @@ -434,23 +440,33 @@ export class VaultComponent implements OnInit, OnDestroy { } this.showAddAccessToggle = false; - let collectionsToReturn = []; + let searchableCollectionNodes: TreeNode[] = []; if (filter.collectionId === undefined || filter.collectionId === All) { - collectionsToReturn = collections.map((c) => c.node); + searchableCollectionNodes = collections; } else { const selectedCollection = ServiceUtils.getTreeNodeObjectFromList( collections, filter.collectionId, ); - collectionsToReturn = selectedCollection?.children.map((c) => c.node) ?? []; + searchableCollectionNodes = selectedCollection?.children ?? []; } + let collectionsToReturn: CollectionAdminView[] = []; + if (await this.searchService.isSearchable(this.userId, searchText)) { + // Flatten the tree for searching through all levels + const flatCollectionTree: CollectionAdminView[] = + getFlatCollectionTree(searchableCollectionNodes); + collectionsToReturn = this.searchPipe.transform( - collectionsToReturn, + flatCollectionTree, searchText, - (collection: CollectionAdminView) => collection.name, - (collection: CollectionAdminView) => collection.id, + (collection) => collection.name, + (collection) => collection.id, + ); + } else { + collectionsToReturn = searchableCollectionNodes.map( + (treeNode: TreeNode): CollectionAdminView => treeNode.node, ); } @@ -620,9 +636,23 @@ export class VaultComponent implements OnInit, OnDestroy { ) .subscribe(); - this.unpaidSubscriptionDialog$.pipe(takeUntil(this.destroy$)).subscribe(); + // Billing Warnings + this.useOrganizationWarningsService$ = this.configService.getFeatureFlag$( + FeatureFlag.UseOrganizationWarningsService, + ); - this.freeTrial$ = combineLatest([ + this.useOrganizationWarningsService$ + .pipe( + switchMap((enabled) => + enabled + ? this.organizationWarningsService.showInactiveSubscriptionDialog$(this.organization) + : this.unpaidSubscriptionDialog$, + ), + takeUntil(this.destroy$), + ) + .subscribe(); + + const freeTrial$ = combineLatest([ organization$, this.hasSubscription$.pipe(filter((hasSubscription) => hasSubscription !== null)), ]).pipe( @@ -647,7 +677,12 @@ export class VaultComponent implements OnInit, OnDestroy { filter((result) => result !== null), ); - this.resellerWarning$ = organization$.pipe( + this.freeTrialWhenWarningsServiceDisabled$ = this.useOrganizationWarningsService$.pipe( + filter((enabled) => !enabled), + switchMap(() => freeTrial$), + ); + + const resellerWarning$ = organization$.pipe( filter((org) => org.isOwner), switchMap((org) => from(this.billingApiService.getOrganizationBillingMetadata(org.id)).pipe( @@ -657,6 +692,12 @@ export class VaultComponent implements OnInit, OnDestroy { map(({ org, metadata }) => this.resellerWarningService.getWarning(org, metadata)), ); + this.resellerWarningWhenWarningsServiceDisabled$ = this.useOrganizationWarningsService$.pipe( + filter((enabled) => !enabled), + switchMap(() => resellerWarning$), + ); + // End Billing Warnings + firstSetup$ .pipe( switchMap(() => this.refresh$), @@ -811,6 +852,7 @@ export class VaultComponent implements OnInit, OnDestroy { const dialogRef = AttachmentsV2Component.open(this.dialogService, { cipherId: cipher.id as CipherId, + organizationId: cipher.organizationId as OrganizationId, }); const result = await firstValueFrom(dialogRef.closed); 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 2079d592a28..02be3476ad5 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 @@ -63,7 +63,7 @@
- {{ "limitedEventLogs" | i18n: ProductTierType[organization?.productTierType] }} + {{ "upgradeEventLogTitleMessage" | i18n }}
- {{ "upgradeForFullEvents" | i18n }} + {{ "upgradeForFullEventsMessage" | i18n }}
+ {{ "sponsorshipFreeBitwardenFamilies" | i18n }} +
{{ "sponsoredFamiliesRemoveActiveSponsorship" | i18n }}
{{ "nosponsoredFamiliesDetails" | i18n }}
- {{ "membersWithSponsoredFamilies" | i18n }} -
{{ "noMemberFamiliesDescription" | i18n }}
- {{ "sponsorFreeBitwardenFamilies" | i18n }} -
{{ "noSponsoredFamiliesDescription" | i18n }}
diff --git a/apps/web/src/app/billing/shared/payment/payment.component.html b/apps/web/src/app/billing/shared/payment/payment.component.html index c86975cd0e8..0d76d98e334 100644 --- a/apps/web/src/app/billing/shared/payment/payment.component.html +++ b/apps/web/src/app/billing/shared/payment/payment.component.html @@ -81,7 +81,7 @@ - {{ "verifyBankAccountWithStatementDescriptorWarning" | i18n }} + {{ bankAccountWarning }} diff --git a/apps/web/src/app/billing/shared/payment/payment.component.ts b/apps/web/src/app/billing/shared/payment/payment.component.ts index c7c3e31c89f..5911e377869 100644 --- a/apps/web/src/app/billing/shared/payment/payment.component.ts +++ b/apps/web/src/app/billing/shared/payment/payment.component.ts @@ -8,6 +8,7 @@ import { takeUntil } from "rxjs/operators"; import { BillingApiServiceAbstraction } from "@bitwarden/common/billing/abstractions"; import { PaymentMethodType } from "@bitwarden/common/billing/enums"; import { TokenizedPaymentSourceRequest } from "@bitwarden/common/billing/models/request/tokenized-payment-source.request"; +import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { SharedModule } from "../../../shared"; import { BillingServicesModule, BraintreeService, StripeService } from "../../services"; @@ -37,6 +38,8 @@ export class PaymentComponent implements OnInit, OnDestroy { /** If provided, will be invoked with the tokenized payment source during form submission. */ @Input() protected onSubmit?: (request: TokenizedPaymentSourceRequest) => Promise; + @Input() private bankAccountWarningOverride?: string; + @Output() submitted = new EventEmitter(); private destroy$ = new Subject(); @@ -56,6 +59,7 @@ export class PaymentComponent implements OnInit, OnDestroy { constructor( private billingApiService: BillingApiServiceAbstraction, private braintreeService: BraintreeService, + private i18nService: I18nService, private stripeService: StripeService, ) {} @@ -200,4 +204,12 @@ export class PaymentComponent implements OnInit, OnDestroy { private get usingStripe(): boolean { return this.usingBankAccount || this.usingCard; } + + get bankAccountWarning(): string { + if (this.bankAccountWarningOverride) { + return this.bankAccountWarningOverride; + } else { + return this.i18nService.t("verifyBankAccountWithStatementDescriptorWarning"); + } + } } diff --git a/apps/web/src/app/billing/trial-initiation/content/abm-enterprise-content.component.html b/apps/web/src/app/billing/trial-initiation/content/abm-enterprise-content.component.html deleted file mode 100644 index 46e1fae80df..00000000000 --- a/apps/web/src/app/billing/trial-initiation/content/abm-enterprise-content.component.html +++ /dev/null @@ -1,17 +0,0 @@ -The Bitwarden Password Manager - - - Trusted by millions of individuals, teams, and organizations worldwide for secure password - storage and sharing. - - - - Store logins, secure notes, and more - Collaborate and share securely - Access anywhere on any device - Create your account to get started - - - - - diff --git a/apps/web/src/app/billing/trial-initiation/content/abm-enterprise-content.component.ts b/apps/web/src/app/billing/trial-initiation/content/abm-enterprise-content.component.ts deleted file mode 100644 index 0f9db7b4405..00000000000 --- a/apps/web/src/app/billing/trial-initiation/content/abm-enterprise-content.component.ts +++ /dev/null @@ -1,7 +0,0 @@ -import { Component } from "@angular/core"; - -@Component({ - selector: "app-abm-enterprise-content", - templateUrl: "abm-enterprise-content.component.html", -}) -export class AbmEnterpriseContentComponent {} diff --git a/apps/web/src/app/billing/trial-initiation/content/abm-teams-content.component.html b/apps/web/src/app/billing/trial-initiation/content/abm-teams-content.component.html deleted file mode 100644 index 46e1fae80df..00000000000 --- a/apps/web/src/app/billing/trial-initiation/content/abm-teams-content.component.html +++ /dev/null @@ -1,17 +0,0 @@ -The Bitwarden Password Manager - - - Trusted by millions of individuals, teams, and organizations worldwide for secure password - storage and sharing. - - - - Store logins, secure notes, and more - Collaborate and share securely - Access anywhere on any device - Create your account to get started - - - - - diff --git a/apps/web/src/app/billing/trial-initiation/content/abm-teams-content.component.ts b/apps/web/src/app/billing/trial-initiation/content/abm-teams-content.component.ts deleted file mode 100644 index 7765555f5cc..00000000000 --- a/apps/web/src/app/billing/trial-initiation/content/abm-teams-content.component.ts +++ /dev/null @@ -1,7 +0,0 @@ -import { Component } from "@angular/core"; - -@Component({ - selector: "app-abm-teams-content", - templateUrl: "abm-teams-content.component.html", -}) -export class AbmTeamsContentComponent {} diff --git a/apps/web/src/app/billing/trial-initiation/content/cnet-enterprise-content.component.html b/apps/web/src/app/billing/trial-initiation/content/cnet-enterprise-content.component.html deleted file mode 100644 index b5c16911ab0..00000000000 --- a/apps/web/src/app/billing/trial-initiation/content/cnet-enterprise-content.component.html +++ /dev/null @@ -1,17 +0,0 @@ -Start Your Enterprise Free Trial Now - - - Millions of individuals, teams, and organizations worldwide trust Bitwarden for secure password - storage and sharing. - - - - Collaborate and share securely - Deploy and manage quickly and easily - Access anywhere on any device - Create your account to get started - - - - - diff --git a/apps/web/src/app/billing/trial-initiation/content/cnet-enterprise-content.component.ts b/apps/web/src/app/billing/trial-initiation/content/cnet-enterprise-content.component.ts deleted file mode 100644 index 4a6de8d3003..00000000000 --- a/apps/web/src/app/billing/trial-initiation/content/cnet-enterprise-content.component.ts +++ /dev/null @@ -1,7 +0,0 @@ -import { Component } from "@angular/core"; - -@Component({ - selector: "app-cnet-enterprise-content", - templateUrl: "cnet-enterprise-content.component.html", -}) -export class CnetEnterpriseContentComponent {} diff --git a/apps/web/src/app/billing/trial-initiation/content/cnet-individual-content.component.html b/apps/web/src/app/billing/trial-initiation/content/cnet-individual-content.component.html deleted file mode 100644 index 6e6f545c170..00000000000 --- a/apps/web/src/app/billing/trial-initiation/content/cnet-individual-content.component.html +++ /dev/null @@ -1,17 +0,0 @@ -Start Your Premium Account Now - - - Millions of individuals, teams, and organizations worldwide trust Bitwarden for secure password - storage and sharing. - - - - Store logins, secure notes, and more - Secure your account with advanced two-step login - Access anywhere on any device - Create your account to get started - - - - - diff --git a/apps/web/src/app/billing/trial-initiation/content/cnet-individual-content.component.ts b/apps/web/src/app/billing/trial-initiation/content/cnet-individual-content.component.ts deleted file mode 100644 index 56d8b37af90..00000000000 --- a/apps/web/src/app/billing/trial-initiation/content/cnet-individual-content.component.ts +++ /dev/null @@ -1,7 +0,0 @@ -import { Component } from "@angular/core"; - -@Component({ - selector: "app-cnet-individual-content", - templateUrl: "cnet-individual-content.component.html", -}) -export class CnetIndividualContentComponent {} diff --git a/apps/web/src/app/billing/trial-initiation/content/cnet-teams-content.component.html b/apps/web/src/app/billing/trial-initiation/content/cnet-teams-content.component.html deleted file mode 100644 index c719c5ac7ce..00000000000 --- a/apps/web/src/app/billing/trial-initiation/content/cnet-teams-content.component.html +++ /dev/null @@ -1,17 +0,0 @@ -Start Your Teams Free Trial Now - - - Millions of individuals, teams, and organizations worldwide trust Bitwarden for secure password - storage and sharing. - - - - Collaborate and share securely - Deploy and manage quickly and easily - Access anywhere on any device - Create your account to get started - - - - - diff --git a/apps/web/src/app/billing/trial-initiation/content/cnet-teams-content.component.ts b/apps/web/src/app/billing/trial-initiation/content/cnet-teams-content.component.ts deleted file mode 100644 index ff79a0d37cd..00000000000 --- a/apps/web/src/app/billing/trial-initiation/content/cnet-teams-content.component.ts +++ /dev/null @@ -1,7 +0,0 @@ -import { Component } from "@angular/core"; - -@Component({ - selector: "app-cnet-teams-content", - templateUrl: "cnet-teams-content.component.html", -}) -export class CnetTeamsContentComponent {} diff --git a/apps/web/src/app/billing/trial-initiation/content/default-content.component.html b/apps/web/src/app/billing/trial-initiation/content/default-content.component.html deleted file mode 100644 index e1839517ff6..00000000000 --- a/apps/web/src/app/billing/trial-initiation/content/default-content.component.html +++ /dev/null @@ -1,16 +0,0 @@ -The Bitwarden Password Manager - - - Trusted by millions of individuals, teams, and organizations worldwide for secure password - storage and sharing. - - - - Store logins, secure notes, and more - Collaborate and share securely - Access anywhere on any device - Create your account to get started - - - - diff --git a/apps/web/src/app/billing/trial-initiation/content/default-content.component.ts b/apps/web/src/app/billing/trial-initiation/content/default-content.component.ts deleted file mode 100644 index 7ad40b089d1..00000000000 --- a/apps/web/src/app/billing/trial-initiation/content/default-content.component.ts +++ /dev/null @@ -1,7 +0,0 @@ -import { Component } from "@angular/core"; - -@Component({ - selector: "app-default-content", - templateUrl: "default-content.component.html", -}) -export class DefaultContentComponent {} diff --git a/apps/web/src/app/billing/trial-initiation/content/enterprise-content.component.html b/apps/web/src/app/billing/trial-initiation/content/enterprise-content.component.html deleted file mode 100644 index f57fb7a3510..00000000000 --- a/apps/web/src/app/billing/trial-initiation/content/enterprise-content.component.html +++ /dev/null @@ -1,44 +0,0 @@ -Start your 7-day Enterprise free trial - - - Bitwarden is the most trusted password manager designed for seamless administration and employee - usability. - - - - - Instantly and securely share credentials with the groups and individuals who need them - - - Strengthen company-wide security through centralized administrative control and - policies - - - Streamline user onboarding and automate account provisioning with flexible SSO and SCIM - integrations - - - Migrate to Bitwarden in minutes with comprehensive import options - - - Give all Enterprise users the gift of 360º security with a free Families plan - - - - - diff --git a/apps/web/src/app/billing/trial-initiation/content/enterprise-content.component.ts b/apps/web/src/app/billing/trial-initiation/content/enterprise-content.component.ts deleted file mode 100644 index 847b3c3088a..00000000000 --- a/apps/web/src/app/billing/trial-initiation/content/enterprise-content.component.ts +++ /dev/null @@ -1,7 +0,0 @@ -import { Component } from "@angular/core"; - -@Component({ - selector: "app-enterprise-content", - templateUrl: "enterprise-content.component.html", -}) -export class EnterpriseContentComponent {} diff --git a/apps/web/src/app/billing/trial-initiation/content/enterprise1-content.component.html b/apps/web/src/app/billing/trial-initiation/content/enterprise1-content.component.html deleted file mode 100644 index f57fb7a3510..00000000000 --- a/apps/web/src/app/billing/trial-initiation/content/enterprise1-content.component.html +++ /dev/null @@ -1,44 +0,0 @@ -Start your 7-day Enterprise free trial - - - Bitwarden is the most trusted password manager designed for seamless administration and employee - usability. - - - - - Instantly and securely share credentials with the groups and individuals who need them - - - Strengthen company-wide security through centralized administrative control and - policies - - - Streamline user onboarding and automate account provisioning with flexible SSO and SCIM - integrations - - - Migrate to Bitwarden in minutes with comprehensive import options - - - Give all Enterprise users the gift of 360º security with a free Families plan - - - - - diff --git a/apps/web/src/app/billing/trial-initiation/content/enterprise1-content.component.ts b/apps/web/src/app/billing/trial-initiation/content/enterprise1-content.component.ts deleted file mode 100644 index 7b1199eb421..00000000000 --- a/apps/web/src/app/billing/trial-initiation/content/enterprise1-content.component.ts +++ /dev/null @@ -1,7 +0,0 @@ -import { Component } from "@angular/core"; - -@Component({ - selector: "app-enterprise1-content", - templateUrl: "enterprise1-content.component.html", -}) -export class Enterprise1ContentComponent {} diff --git a/apps/web/src/app/billing/trial-initiation/content/enterprise2-content.component.html b/apps/web/src/app/billing/trial-initiation/content/enterprise2-content.component.html deleted file mode 100644 index f57fb7a3510..00000000000 --- a/apps/web/src/app/billing/trial-initiation/content/enterprise2-content.component.html +++ /dev/null @@ -1,44 +0,0 @@ -Start your 7-day Enterprise free trial - - - Bitwarden is the most trusted password manager designed for seamless administration and employee - usability. - - - - - Instantly and securely share credentials with the groups and individuals who need them - - - Strengthen company-wide security through centralized administrative control and - policies - - - Streamline user onboarding and automate account provisioning with flexible SSO and SCIM - integrations - - - Migrate to Bitwarden in minutes with comprehensive import options - - - Give all Enterprise users the gift of 360º security with a free Families plan - - - - - diff --git a/apps/web/src/app/billing/trial-initiation/content/enterprise2-content.component.ts b/apps/web/src/app/billing/trial-initiation/content/enterprise2-content.component.ts deleted file mode 100644 index 08dec6190c7..00000000000 --- a/apps/web/src/app/billing/trial-initiation/content/enterprise2-content.component.ts +++ /dev/null @@ -1,7 +0,0 @@ -import { Component } from "@angular/core"; - -@Component({ - selector: "app-enterprise2-content", - templateUrl: "enterprise2-content.component.html", -}) -export class Enterprise2ContentComponent {} diff --git a/apps/web/src/app/billing/trial-initiation/content/logo-badges.component.html b/apps/web/src/app/billing/trial-initiation/content/logo-badges.component.html deleted file mode 100644 index d1b33eab3a4..00000000000 --- a/apps/web/src/app/billing/trial-initiation/content/logo-badges.component.html +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - - diff --git a/apps/web/src/app/billing/trial-initiation/content/logo-badges.component.ts b/apps/web/src/app/billing/trial-initiation/content/logo-badges.component.ts deleted file mode 100644 index c23432b67cf..00000000000 --- a/apps/web/src/app/billing/trial-initiation/content/logo-badges.component.ts +++ /dev/null @@ -1,7 +0,0 @@ -import { Component } from "@angular/core"; - -@Component({ - selector: "app-logo-badges", - templateUrl: "logo-badges.component.html", -}) -export class LogoBadgesComponent {} diff --git a/apps/web/src/app/billing/trial-initiation/content/logo-cnet-5-stars.component.html b/apps/web/src/app/billing/trial-initiation/content/logo-cnet-5-stars.component.html deleted file mode 100644 index fb4537d2820..00000000000 --- a/apps/web/src/app/billing/trial-initiation/content/logo-cnet-5-stars.component.html +++ /dev/null @@ -1,23 +0,0 @@ - - - - - - - - - - “Bitwarden scores points for being fully open-source, secure and audited annually by third-party - cybersecurity firms, giving it a level of transparency that sets it apart from its peers.” - - - - - - Best Password Manager in 2024 - - diff --git a/apps/web/src/app/billing/trial-initiation/content/logo-cnet-5-stars.component.ts b/apps/web/src/app/billing/trial-initiation/content/logo-cnet-5-stars.component.ts deleted file mode 100644 index af531829d50..00000000000 --- a/apps/web/src/app/billing/trial-initiation/content/logo-cnet-5-stars.component.ts +++ /dev/null @@ -1,7 +0,0 @@ -import { Component } from "@angular/core"; - -@Component({ - selector: "app-logo-cnet-5-stars", - templateUrl: "logo-cnet-5-stars.component.html", -}) -export class LogoCnet5StarsComponent {} diff --git a/apps/web/src/app/billing/trial-initiation/content/logo-cnet.component.html b/apps/web/src/app/billing/trial-initiation/content/logo-cnet.component.html deleted file mode 100644 index 4e04cec6da4..00000000000 --- a/apps/web/src/app/billing/trial-initiation/content/logo-cnet.component.html +++ /dev/null @@ -1,15 +0,0 @@ - - - - - - - - "No more excuses; start using Bitwarden today. The identity you save could be your own. The - money definitely will be." - - diff --git a/apps/web/src/app/billing/trial-initiation/content/logo-cnet.component.ts b/apps/web/src/app/billing/trial-initiation/content/logo-cnet.component.ts deleted file mode 100644 index 4f755f66a86..00000000000 --- a/apps/web/src/app/billing/trial-initiation/content/logo-cnet.component.ts +++ /dev/null @@ -1,7 +0,0 @@ -import { Component } from "@angular/core"; - -@Component({ - selector: "app-logo-cnet", - templateUrl: "logo-cnet.component.html", -}) -export class LogoCnetComponent {} diff --git a/apps/web/src/app/billing/trial-initiation/content/logo-company-testimonial.component.html b/apps/web/src/app/billing/trial-initiation/content/logo-company-testimonial.component.html deleted file mode 100644 index 0b81e0bd216..00000000000 --- a/apps/web/src/app/billing/trial-initiation/content/logo-company-testimonial.component.html +++ /dev/null @@ -1,28 +0,0 @@ - - - Recommended by industry experts - - - - - - - - - - - - - “Bitwarden is currently CNET's top pick for the best password manager, thanks in part to - its commitment to transparency and its unbeatable free tier.” - - Best Password Manager in 2024 - diff --git a/apps/web/src/app/billing/trial-initiation/content/logo-company-testimonial.component.ts b/apps/web/src/app/billing/trial-initiation/content/logo-company-testimonial.component.ts deleted file mode 100644 index 9d9c4471820..00000000000 --- a/apps/web/src/app/billing/trial-initiation/content/logo-company-testimonial.component.ts +++ /dev/null @@ -1,7 +0,0 @@ -import { Component } from "@angular/core"; - -@Component({ - selector: "app-logo-company-testimonial", - templateUrl: "logo-company-testimonial.component.html", -}) -export class LogoCompanyTestimonialComponent {} diff --git a/apps/web/src/app/billing/trial-initiation/content/logo-forbes.component.html b/apps/web/src/app/billing/trial-initiation/content/logo-forbes.component.html deleted file mode 100644 index 34426168324..00000000000 --- a/apps/web/src/app/billing/trial-initiation/content/logo-forbes.component.html +++ /dev/null @@ -1,15 +0,0 @@ - - - - - - - - “Bitwarden boasts the backing of some of the world's best security experts and an attractive, - easy-to-use interface” - - diff --git a/apps/web/src/app/billing/trial-initiation/content/logo-forbes.component.ts b/apps/web/src/app/billing/trial-initiation/content/logo-forbes.component.ts deleted file mode 100644 index 818721fd1e9..00000000000 --- a/apps/web/src/app/billing/trial-initiation/content/logo-forbes.component.ts +++ /dev/null @@ -1,7 +0,0 @@ -import { Component } from "@angular/core"; - -@Component({ - selector: "app-logo-forbes", - templateUrl: "logo-forbes.component.html", -}) -export class LogoForbesComponent {} diff --git a/apps/web/src/app/billing/trial-initiation/content/logo-us-news.component.html b/apps/web/src/app/billing/trial-initiation/content/logo-us-news.component.html deleted file mode 100644 index bd44b56f090..00000000000 --- a/apps/web/src/app/billing/trial-initiation/content/logo-us-news.component.html +++ /dev/null @@ -1,5 +0,0 @@ - diff --git a/apps/web/src/app/billing/trial-initiation/content/logo-us-news.component.ts b/apps/web/src/app/billing/trial-initiation/content/logo-us-news.component.ts deleted file mode 100644 index fb0b1e0c71b..00000000000 --- a/apps/web/src/app/billing/trial-initiation/content/logo-us-news.component.ts +++ /dev/null @@ -1,7 +0,0 @@ -import { Component } from "@angular/core"; - -@Component({ - selector: "app-logo-us-news", - templateUrl: "logo-us-news.component.html", -}) -export class LogoUSNewsComponent {} diff --git a/apps/web/src/app/billing/trial-initiation/content/review-blurb.component.html b/apps/web/src/app/billing/trial-initiation/content/review-blurb.component.html deleted file mode 100644 index cd719a35af8..00000000000 --- a/apps/web/src/app/billing/trial-initiation/content/review-blurb.component.html +++ /dev/null @@ -1,13 +0,0 @@ - - - {{ header }} - - - "{{ quote }}" - - - - {{ source }} - - - diff --git a/apps/web/src/app/billing/trial-initiation/content/review-blurb.component.ts b/apps/web/src/app/billing/trial-initiation/content/review-blurb.component.ts deleted file mode 100644 index 6419ddf1e45..00000000000 --- a/apps/web/src/app/billing/trial-initiation/content/review-blurb.component.ts +++ /dev/null @@ -1,13 +0,0 @@ -// FIXME: Update this file to be type safe and remove this and next line -// @ts-strict-ignore -import { Component, Input } from "@angular/core"; - -@Component({ - selector: "app-review-blurb", - templateUrl: "review-blurb.component.html", -}) -export class ReviewBlurbComponent { - @Input() header: string; - @Input() quote: string; - @Input() source: string; -} diff --git a/apps/web/src/app/billing/trial-initiation/content/review-logo.component.html b/apps/web/src/app/billing/trial-initiation/content/review-logo.component.html deleted file mode 100644 index 77f592f1c45..00000000000 --- a/apps/web/src/app/billing/trial-initiation/content/review-logo.component.html +++ /dev/null @@ -1,18 +0,0 @@ - - - - - - - - - - - - - - - - 4.7 - - diff --git a/apps/web/src/app/billing/trial-initiation/content/review-logo.component.ts b/apps/web/src/app/billing/trial-initiation/content/review-logo.component.ts deleted file mode 100644 index 9b104ac0bc3..00000000000 --- a/apps/web/src/app/billing/trial-initiation/content/review-logo.component.ts +++ /dev/null @@ -1,13 +0,0 @@ -// FIXME: Update this file to be type safe and remove this and next line -// @ts-strict-ignore -import { Component, Input } from "@angular/core"; - -@Component({ - selector: "review-logo", - templateUrl: "review-logo.component.html", -}) -export class ReviewLogoComponent { - @Input() logoClass: string; - @Input() logoSrc: string; - @Input() logoAlt: string; -} diff --git a/apps/web/src/app/billing/trial-initiation/content/secrets-manager-content.component.html b/apps/web/src/app/billing/trial-initiation/content/secrets-manager-content.component.html deleted file mode 100644 index 569ff91f625..00000000000 --- a/apps/web/src/app/billing/trial-initiation/content/secrets-manager-content.component.html +++ /dev/null @@ -1,30 +0,0 @@ -{{ header }} - - - {{ headline }} - - - - - {{ primaryPoint }} - - - - - - {{ calloutHeadline }} - - - {{ callout }} - - - - - - - - diff --git a/apps/web/src/app/billing/trial-initiation/content/secrets-manager-content.component.ts b/apps/web/src/app/billing/trial-initiation/content/secrets-manager-content.component.ts deleted file mode 100644 index 955c18fddf2..00000000000 --- a/apps/web/src/app/billing/trial-initiation/content/secrets-manager-content.component.ts +++ /dev/null @@ -1,80 +0,0 @@ -// 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 { ActivatedRoute } from "@angular/router"; -import { Subject, takeUntil } from "rxjs"; - -@Component({ - selector: "app-secrets-manager-content", - templateUrl: "secrets-manager-content.component.html", -}) -export class SecretsManagerContentComponent implements OnInit, OnDestroy { - header: string; - headline = - "A simpler, faster way to secure and automate secrets across code and infrastructure deployments"; - primaryPoints: string[]; - calloutHeadline: string; - callouts: string[]; - - private paidPrimaryPoints = [ - "Unlimited secrets, users, and projects", - "Simple and transparent pricing", - "Zero-knowledge, end-to-end encryption", - ]; - - private paidCalloutHeadline = "Limited time offer"; - - private paidCallouts = [ - "Sign up today and receive a complimentary 12-month subscription to Bitwarden Password Manager", - "Experience complete security across your organization", - "Secure all your sensitive credentials, from user applications to machine secrets", - ]; - - private freePrimaryPoints = [ - "Unlimited secrets", - "Simple and transparent pricing", - "Zero-knowledge, end-to-end encryption", - ]; - - private freeCalloutHeadline = "Go beyond developer security!"; - - private freeCallouts = [ - "Your Bitwarden account will also grant complimentary access to Bitwarden Password Manager", - "Extend end-to-end encryption to your personal passwords, addresses, credit cards and notes", - ]; - - private destroy$ = new Subject(); - - constructor(private activatedRoute: ActivatedRoute) {} - - ngOnDestroy() { - this.destroy$.next(); - this.destroy$.complete(); - } - - ngOnInit(): void { - this.activatedRoute.queryParams.pipe(takeUntil(this.destroy$)).subscribe((queryParameters) => { - switch (queryParameters.org) { - case "enterprise": - this.header = "Secrets Manager for Enterprise"; - this.primaryPoints = this.paidPrimaryPoints; - this.calloutHeadline = this.paidCalloutHeadline; - this.callouts = this.paidCallouts; - break; - case "free": - this.header = "Bitwarden Secrets Manager"; - this.primaryPoints = this.freePrimaryPoints; - this.calloutHeadline = this.freeCalloutHeadline; - this.callouts = this.freeCallouts; - break; - case "teams": - case "teamsStarter": - this.header = "Secrets Manager for Teams"; - this.primaryPoints = this.paidPrimaryPoints; - this.calloutHeadline = this.paidCalloutHeadline; - this.callouts = this.paidCallouts; - break; - } - }); - } -} diff --git a/apps/web/src/app/billing/trial-initiation/content/teams-content.component.html b/apps/web/src/app/billing/trial-initiation/content/teams-content.component.html deleted file mode 100644 index 46e1fae80df..00000000000 --- a/apps/web/src/app/billing/trial-initiation/content/teams-content.component.html +++ /dev/null @@ -1,17 +0,0 @@ -The Bitwarden Password Manager - - - Trusted by millions of individuals, teams, and organizations worldwide for secure password - storage and sharing. - - - - Store logins, secure notes, and more - Collaborate and share securely - Access anywhere on any device - Create your account to get started - - - - - diff --git a/apps/web/src/app/billing/trial-initiation/content/teams-content.component.ts b/apps/web/src/app/billing/trial-initiation/content/teams-content.component.ts deleted file mode 100644 index 5c97695deff..00000000000 --- a/apps/web/src/app/billing/trial-initiation/content/teams-content.component.ts +++ /dev/null @@ -1,7 +0,0 @@ -import { Component } from "@angular/core"; - -@Component({ - selector: "app-teams-content", - templateUrl: "teams-content.component.html", -}) -export class TeamsContentComponent {} diff --git a/apps/web/src/app/billing/trial-initiation/content/teams1-content.component.html b/apps/web/src/app/billing/trial-initiation/content/teams1-content.component.html deleted file mode 100644 index f51c370bebd..00000000000 --- a/apps/web/src/app/billing/trial-initiation/content/teams1-content.component.html +++ /dev/null @@ -1,35 +0,0 @@ -Start your 7-day free trial for Teams - - - Strengthen business security with an easy-to-use password manager your team will love. - - - - - Instantly and securely share credentials with the groups and individuals who need them - - - Migrate to Bitwarden in minutes with comprehensive import options - - - Save time and increase productivity with autofill and instant device syncing - - - Enhance security practices across your team with easy user management - - - - - diff --git a/apps/web/src/app/billing/trial-initiation/content/teams1-content.component.ts b/apps/web/src/app/billing/trial-initiation/content/teams1-content.component.ts deleted file mode 100644 index 055ec7fda10..00000000000 --- a/apps/web/src/app/billing/trial-initiation/content/teams1-content.component.ts +++ /dev/null @@ -1,7 +0,0 @@ -import { Component } from "@angular/core"; - -@Component({ - selector: "app-teams1-content", - templateUrl: "teams1-content.component.html", -}) -export class Teams1ContentComponent {} diff --git a/apps/web/src/app/billing/trial-initiation/content/teams2-content.component.html b/apps/web/src/app/billing/trial-initiation/content/teams2-content.component.html deleted file mode 100644 index f51c370bebd..00000000000 --- a/apps/web/src/app/billing/trial-initiation/content/teams2-content.component.html +++ /dev/null @@ -1,35 +0,0 @@ -Start your 7-day free trial for Teams - - - Strengthen business security with an easy-to-use password manager your team will love. - - - - - Instantly and securely share credentials with the groups and individuals who need them - - - Migrate to Bitwarden in minutes with comprehensive import options - - - Save time and increase productivity with autofill and instant device syncing - - - Enhance security practices across your team with easy user management - - - - - diff --git a/apps/web/src/app/billing/trial-initiation/content/teams2-content.component.ts b/apps/web/src/app/billing/trial-initiation/content/teams2-content.component.ts deleted file mode 100644 index 394ba90b491..00000000000 --- a/apps/web/src/app/billing/trial-initiation/content/teams2-content.component.ts +++ /dev/null @@ -1,7 +0,0 @@ -import { Component } from "@angular/core"; - -@Component({ - selector: "app-teams2-content", - templateUrl: "teams2-content.component.html", -}) -export class Teams2ContentComponent {} diff --git a/apps/web/src/app/billing/trial-initiation/content/teams3-content.component.html b/apps/web/src/app/billing/trial-initiation/content/teams3-content.component.html deleted file mode 100644 index c6f1ae697ae..00000000000 --- a/apps/web/src/app/billing/trial-initiation/content/teams3-content.component.html +++ /dev/null @@ -1,26 +0,0 @@ -Begin Teams Starter Free Trial Now - - - Millions of individuals, teams, and organizations worldwide trust Bitwarden for secure password - storage and sharing. - - - - - Powerful security for up to 10 users - - Have more than 10 users? - Start a Teams trial - - - Collaborate and share securely - Deploy and manage quickly and easily - Access anywhere on any device - Create your account to get started - - - - - diff --git a/apps/web/src/app/billing/trial-initiation/content/teams3-content.component.ts b/apps/web/src/app/billing/trial-initiation/content/teams3-content.component.ts deleted file mode 100644 index df91268ab26..00000000000 --- a/apps/web/src/app/billing/trial-initiation/content/teams3-content.component.ts +++ /dev/null @@ -1,7 +0,0 @@ -import { Component } from "@angular/core"; - -@Component({ - selector: "app-teams3-content", - templateUrl: "teams3-content.component.html", -}) -export class Teams3ContentComponent {} diff --git a/apps/web/src/app/billing/trial-initiation/secrets-manager/secrets-manager-trial-free-stepper.component.html b/apps/web/src/app/billing/trial-initiation/secrets-manager/secrets-manager-trial-free-stepper.component.html deleted file mode 100644 index dddac598a46..00000000000 --- a/apps/web/src/app/billing/trial-initiation/secrets-manager/secrets-manager-trial-free-stepper.component.html +++ /dev/null @@ -1,45 +0,0 @@ - - - - - {{ "next" | i18n }} - - - - - {{ "smFreeTrialThankYou" | i18n }} - - - - {{ "smFreeTrialConfirmationEmail" | i18n }} - {{ formGroup.get("email").value }}. - - - - - - - {{ "getStarted" | i18n | titlecase }} - - - {{ "inviteUsers" | i18n }} - - - - diff --git a/apps/web/src/app/billing/trial-initiation/secrets-manager/secrets-manager-trial-free-stepper.component.ts b/apps/web/src/app/billing/trial-initiation/secrets-manager/secrets-manager-trial-free-stepper.component.ts deleted file mode 100644 index f7c5a9b2b98..00000000000 --- a/apps/web/src/app/billing/trial-initiation/secrets-manager/secrets-manager-trial-free-stepper.component.ts +++ /dev/null @@ -1,90 +0,0 @@ -// FIXME: Update this file to be type safe and remove this and next line -// @ts-strict-ignore -import { Component, OnInit, ViewChild } from "@angular/core"; -import { UntypedFormBuilder, Validators } from "@angular/forms"; -import { Router } from "@angular/router"; - -import { OrganizationBillingServiceAbstraction as OrganizationBillingService } from "@bitwarden/common/billing/abstractions/organization-billing.service"; -import { PlanType } from "@bitwarden/common/billing/enums"; -import { ReferenceEventRequest } from "@bitwarden/common/models/request/reference-event.request"; -import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; - -import { VerticalStepperComponent } from "../../trial-initiation/vertical-stepper/vertical-stepper.component"; - -@Component({ - selector: "app-secrets-manager-trial-free-stepper", - templateUrl: "secrets-manager-trial-free-stepper.component.html", -}) -export class SecretsManagerTrialFreeStepperComponent implements OnInit { - @ViewChild("stepper", { static: false }) verticalStepper: VerticalStepperComponent; - - formGroup = this.formBuilder.group({ - name: [ - "", - { - validators: [Validators.required, Validators.maxLength(50)], - updateOn: "change", - }, - ], - email: [ - "", - { - validators: [Validators.email], - }, - ], - }); - - subLabels = { - createAccount: - "Before creating your free organization, you first need to log in or create a personal account.", - organizationInfo: "Enter your organization information", - }; - - organizationId: string; - - referenceEventRequest: ReferenceEventRequest; - - constructor( - protected formBuilder: UntypedFormBuilder, - protected i18nService: I18nService, - protected organizationBillingService: OrganizationBillingService, - protected router: Router, - ) {} - - ngOnInit(): void { - this.referenceEventRequest = new ReferenceEventRequest(); - this.referenceEventRequest.initiationPath = "Secrets Manager trial from marketing website"; - } - - accountCreated(email: string): void { - this.formGroup.get("email")?.setValue(email); - this.subLabels.createAccount = email; - this.verticalStepper.next(); - } - - async createOrganization(): Promise { - const response = await this.organizationBillingService.startFree({ - organization: { - name: this.formGroup.get("name").value, - billingEmail: this.formGroup.get("email").value, - }, - plan: { - type: PlanType.Free, - subscribeToSecretsManager: true, - isFromSecretsManagerTrial: true, - }, - }); - - this.organizationId = response.id; - this.subLabels.organizationInfo = response.name; - this.verticalStepper.next(); - } - - async navigateToMembers(): Promise { - await this.router.navigate(["organizations", this.organizationId, "members"]); - } - - async navigateToSecretsManager(): Promise { - await this.router.navigate(["sm", this.organizationId]); - } -} diff --git a/apps/web/src/app/billing/trial-initiation/secrets-manager/secrets-manager-trial-paid-stepper.component.html b/apps/web/src/app/billing/trial-initiation/secrets-manager/secrets-manager-trial-paid-stepper.component.html deleted file mode 100644 index 99e2706d713..00000000000 --- a/apps/web/src/app/billing/trial-initiation/secrets-manager/secrets-manager-trial-paid-stepper.component.html +++ /dev/null @@ -1,67 +0,0 @@ - - - - - {{ "startTrial" | i18n }} - - - {{ "next" | i18n }} - - - - - - - - - - {{ "getStarted" | i18n | titlecase }} - - - {{ "inviteUsers" | i18n }} - - - - diff --git a/apps/web/src/app/billing/trial-initiation/secrets-manager/secrets-manager-trial-paid-stepper.component.ts b/apps/web/src/app/billing/trial-initiation/secrets-manager/secrets-manager-trial-paid-stepper.component.ts deleted file mode 100644 index 650c1d8e69e..00000000000 --- a/apps/web/src/app/billing/trial-initiation/secrets-manager/secrets-manager-trial-paid-stepper.component.ts +++ /dev/null @@ -1,144 +0,0 @@ -// FIXME: Update this file to be type safe and remove this and next line -// @ts-strict-ignore -import { Component, Input, OnInit, ViewChild } from "@angular/core"; -import { UntypedFormBuilder } from "@angular/forms"; -import { ActivatedRoute, Router } from "@angular/router"; -import { Subject, takeUntil } from "rxjs"; - -import { OrganizationBillingServiceAbstraction as OrganizationBillingService } from "@bitwarden/common/billing/abstractions/organization-billing.service"; -import { PlanType, ProductTierType } from "@bitwarden/common/billing/enums"; -import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum"; -import { ReferenceEventRequest } from "@bitwarden/common/models/request/reference-event.request"; -import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; -import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; - -import { - OrganizationCreatedEvent, - SubscriptionProduct, - TrialOrganizationType, -} from "../../../billing/accounts/trial-initiation/trial-billing-step.component"; -import { VerticalStepperComponent } from "../../trial-initiation/vertical-stepper/vertical-stepper.component"; -import { SecretsManagerTrialFreeStepperComponent } from "../secrets-manager/secrets-manager-trial-free-stepper.component"; - -export enum ValidOrgParams { - families = "families", - enterprise = "enterprise", - teams = "teams", - teamsStarter = "teamsStarter", - individual = "individual", - premium = "premium", - free = "free", -} - -const trialFlowOrgs = [ - ValidOrgParams.teams, - ValidOrgParams.teamsStarter, - ValidOrgParams.enterprise, - ValidOrgParams.families, -]; - -@Component({ - selector: "app-secrets-manager-trial-paid-stepper", - templateUrl: "secrets-manager-trial-paid-stepper.component.html", -}) -export class SecretsManagerTrialPaidStepperComponent - extends SecretsManagerTrialFreeStepperComponent - implements OnInit -{ - @ViewChild("stepper", { static: false }) verticalStepper: VerticalStepperComponent; - @Input() organizationTypeQueryParameter: string; - - plan: PlanType; - createOrganizationLoading = false; - billingSubLabel = this.i18nService.t("billingTrialSubLabel"); - organizationId: string; - - private destroy$ = new Subject(); - protected enableTrialPayment$ = this.configService.getFeatureFlag$( - FeatureFlag.TrialPaymentOptional, - ); - - constructor( - private route: ActivatedRoute, - private configService: ConfigService, - protected formBuilder: UntypedFormBuilder, - protected i18nService: I18nService, - protected organizationBillingService: OrganizationBillingService, - protected router: Router, - ) { - super(formBuilder, i18nService, organizationBillingService, router); - } - - async ngOnInit(): Promise { - this.referenceEventRequest = new ReferenceEventRequest(); - this.referenceEventRequest.initiationPath = "Secrets Manager trial from marketing website"; - - this.route.queryParams.pipe(takeUntil(this.destroy$)).subscribe((qParams) => { - if (trialFlowOrgs.includes(qParams.org)) { - if (qParams.org === ValidOrgParams.teamsStarter) { - this.plan = PlanType.TeamsStarter; - } else if (qParams.org === ValidOrgParams.teams) { - this.plan = PlanType.TeamsAnnually; - } else if (qParams.org === ValidOrgParams.enterprise) { - this.plan = PlanType.EnterpriseAnnually; - } - } - }); - } - - organizationCreated(event: OrganizationCreatedEvent) { - this.organizationId = event.organizationId; - this.billingSubLabel = event.planDescription; - this.verticalStepper.next(); - } - - steppedBack() { - this.verticalStepper.previous(); - } - - async createOrganizationOnTrial(): Promise { - this.createOrganizationLoading = true; - const response = await this.organizationBillingService.purchaseSubscriptionNoPaymentMethod({ - organization: { - name: this.formGroup.get("name").value, - billingEmail: this.formGroup.get("email").value, - initiationPath: "Secrets Manager trial from marketing website", - }, - plan: { - type: this.plan, - subscribeToSecretsManager: true, - isFromSecretsManagerTrial: true, - passwordManagerSeats: 1, - secretsManagerSeats: 1, - }, - }); - - this.organizationId = response?.id; - this.subLabels.organizationInfo = response?.name; - this.createOrganizationLoading = false; - this.verticalStepper.next(); - } - - get createAccountLabel() { - const organizationType = - this.productType === ProductTierType.TeamsStarter - ? "Teams Starter" - : ProductTierType[this.productType]; - return `Before creating your ${organizationType} organization, you first need to log in or create a personal account.`; - } - - get productType(): TrialOrganizationType { - switch (this.organizationTypeQueryParameter) { - case "enterprise": - return ProductTierType.Enterprise; - case "families": - return ProductTierType.Families; - case "teams": - return ProductTierType.Teams; - case "teamsStarter": - return ProductTierType.TeamsStarter; - } - } - - protected readonly SubscriptionProduct = SubscriptionProduct; -} diff --git a/apps/web/src/app/billing/trial-initiation/secrets-manager/secrets-manager-trial.component.html b/apps/web/src/app/billing/trial-initiation/secrets-manager/secrets-manager-trial.component.html deleted file mode 100644 index 88251136dbe..00000000000 --- a/apps/web/src/app/billing/trial-initiation/secrets-manager/secrets-manager-trial.component.html +++ /dev/null @@ -1,44 +0,0 @@ - - - - - - - - - - - - - - - - {{ - "startYour7DayFreeTrialOfBitwardenSecretsManagerFor" - | i18n: organizationTypeQueryParameter - }} - - - - - - - - - - diff --git a/apps/web/src/app/billing/trial-initiation/secrets-manager/secrets-manager-trial.component.ts b/apps/web/src/app/billing/trial-initiation/secrets-manager/secrets-manager-trial.component.ts deleted file mode 100644 index 678514532ca..00000000000 --- a/apps/web/src/app/billing/trial-initiation/secrets-manager/secrets-manager-trial.component.ts +++ /dev/null @@ -1,32 +0,0 @@ -// 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 { ActivatedRoute } from "@angular/router"; -import { Subject, takeUntil } from "rxjs"; - -@Component({ - selector: "app-secrets-manager-trial", - templateUrl: "secrets-manager-trial.component.html", -}) -export class SecretsManagerTrialComponent implements OnInit, OnDestroy { - organizationTypeQueryParameter: string; - - private destroy$ = new Subject(); - - constructor(private route: ActivatedRoute) {} - - ngOnInit(): void { - this.route.queryParams.pipe(takeUntil(this.destroy$)).subscribe((queryParameters) => { - this.organizationTypeQueryParameter = queryParameters.org; - }); - } - - ngOnDestroy(): void { - this.destroy$.next(); - this.destroy$.complete(); - } - - get freeOrganization() { - return this.organizationTypeQueryParameter === "free"; - } -} diff --git a/apps/web/src/app/billing/trial-initiation/trial-initiation.module.ts b/apps/web/src/app/billing/trial-initiation/trial-initiation.module.ts index 3e6bfdc4e6c..06e1cce7f23 100644 --- a/apps/web/src/app/billing/trial-initiation/trial-initiation.module.ts +++ b/apps/web/src/app/billing/trial-initiation/trial-initiation.module.ts @@ -7,36 +7,10 @@ import { FormFieldModule } from "@bitwarden/components"; import { OrganizationCreateModule } from "../../admin-console/organizations/create/organization-create.module"; import { TrialBillingStepComponent } from "../../billing/accounts/trial-initiation/trial-billing-step.component"; -import { SecretsManagerTrialFreeStepperComponent } from "../../billing/trial-initiation/secrets-manager/secrets-manager-trial-free-stepper.component"; -import { SecretsManagerTrialPaidStepperComponent } from "../../billing/trial-initiation/secrets-manager/secrets-manager-trial-paid-stepper.component"; -import { SecretsManagerTrialComponent } from "../../billing/trial-initiation/secrets-manager/secrets-manager-trial.component"; -import { EnvironmentSelectorModule } from "../../components/environment-selector/environment-selector.module"; import { SharedModule } from "../../shared"; import { CompleteTrialInitiationComponent } from "./complete-trial-initiation/complete-trial-initiation.component"; import { ConfirmationDetailsComponent } from "./confirmation-details.component"; -import { AbmEnterpriseContentComponent } from "./content/abm-enterprise-content.component"; -import { AbmTeamsContentComponent } from "./content/abm-teams-content.component"; -import { CnetEnterpriseContentComponent } from "./content/cnet-enterprise-content.component"; -import { CnetIndividualContentComponent } from "./content/cnet-individual-content.component"; -import { CnetTeamsContentComponent } from "./content/cnet-teams-content.component"; -import { DefaultContentComponent } from "./content/default-content.component"; -import { EnterpriseContentComponent } from "./content/enterprise-content.component"; -import { Enterprise1ContentComponent } from "./content/enterprise1-content.component"; -import { Enterprise2ContentComponent } from "./content/enterprise2-content.component"; -import { LogoBadgesComponent } from "./content/logo-badges.component"; -import { LogoCnet5StarsComponent } from "./content/logo-cnet-5-stars.component"; -import { LogoCnetComponent } from "./content/logo-cnet.component"; -import { LogoCompanyTestimonialComponent } from "./content/logo-company-testimonial.component"; -import { LogoForbesComponent } from "./content/logo-forbes.component"; -import { LogoUSNewsComponent } from "./content/logo-us-news.component"; -import { ReviewBlurbComponent } from "./content/review-blurb.component"; -import { ReviewLogoComponent } from "./content/review-logo.component"; -import { SecretsManagerContentComponent } from "./content/secrets-manager-content.component"; -import { TeamsContentComponent } from "./content/teams-content.component"; -import { Teams1ContentComponent } from "./content/teams1-content.component"; -import { Teams2ContentComponent } from "./content/teams2-content.component"; -import { Teams3ContentComponent } from "./content/teams3-content.component"; import { VerticalStepperModule } from "./vertical-stepper/vertical-stepper.module"; @NgModule({ @@ -46,41 +20,10 @@ import { VerticalStepperModule } from "./vertical-stepper/vertical-stepper.modul VerticalStepperModule, FormFieldModule, OrganizationCreateModule, - EnvironmentSelectorModule, TrialBillingStepComponent, InputPasswordComponent, ], - declarations: [ - CompleteTrialInitiationComponent, - EnterpriseContentComponent, - TeamsContentComponent, - ConfirmationDetailsComponent, - DefaultContentComponent, - EnterpriseContentComponent, - Enterprise1ContentComponent, - Enterprise2ContentComponent, - TeamsContentComponent, - Teams1ContentComponent, - Teams2ContentComponent, - Teams3ContentComponent, - CnetEnterpriseContentComponent, - CnetIndividualContentComponent, - CnetTeamsContentComponent, - AbmEnterpriseContentComponent, - AbmTeamsContentComponent, - LogoBadgesComponent, - LogoCnet5StarsComponent, - LogoCompanyTestimonialComponent, - LogoCnetComponent, - LogoForbesComponent, - LogoUSNewsComponent, - ReviewLogoComponent, - SecretsManagerContentComponent, - ReviewBlurbComponent, - SecretsManagerTrialComponent, - SecretsManagerTrialFreeStepperComponent, - SecretsManagerTrialPaidStepperComponent, - ], + declarations: [CompleteTrialInitiationComponent, ConfirmationDetailsComponent], exports: [CompleteTrialInitiationComponent], providers: [TitleCasePipe], }) diff --git a/apps/web/src/app/billing/warnings/free-trial-warning.component.ts b/apps/web/src/app/billing/warnings/free-trial-warning.component.ts new file mode 100644 index 00000000000..e83873e9d6b --- /dev/null +++ b/apps/web/src/app/billing/warnings/free-trial-warning.component.ts @@ -0,0 +1,56 @@ +import { AsyncPipe } from "@angular/common"; +import { Component, EventEmitter, Input, OnInit, Output } from "@angular/core"; +import { Observable } from "rxjs"; + +import { Organization } from "@bitwarden/common/admin-console/models/domain/organization"; +import { AnchorLinkDirective, BannerComponent } from "@bitwarden/components"; +import { I18nPipe } from "@bitwarden/ui-common"; + +import { + FreeTrialWarning, + OrganizationWarningsService, +} from "../services/organization-warnings.service"; + +@Component({ + selector: "app-free-trial-warning", + template: ` + @let warning = freeTrialWarning$ | async; + + @if (warning) { + + {{ warning.message }} + + {{ "clickHereToAddPaymentMethod" | i18n }} + + + } + `, + standalone: true, + imports: [AnchorLinkDirective, AsyncPipe, BannerComponent, I18nPipe], +}) +export class FreeTrialWarningComponent implements OnInit { + @Input({ required: true }) organization!: Organization; + @Output() clicked = new EventEmitter(); + + freeTrialWarning$!: Observable; + + constructor(private organizationWarningsService: OrganizationWarningsService) {} + + ngOnInit() { + this.freeTrialWarning$ = this.organizationWarningsService.getFreeTrialWarning$( + this.organization, + ); + } +} diff --git a/apps/web/src/app/billing/warnings/reseller-renewal-warning.component.ts b/apps/web/src/app/billing/warnings/reseller-renewal-warning.component.ts new file mode 100644 index 00000000000..fc94e85e28d --- /dev/null +++ b/apps/web/src/app/billing/warnings/reseller-renewal-warning.component.ts @@ -0,0 +1,45 @@ +import { AsyncPipe } from "@angular/common"; +import { Component, Input, OnInit } from "@angular/core"; +import { Observable } from "rxjs"; + +import { Organization } from "@bitwarden/common/admin-console/models/domain/organization"; +import { BannerComponent } from "@bitwarden/components"; + +import { + OrganizationWarningsService, + ResellerRenewalWarning, +} from "../services/organization-warnings.service"; + +@Component({ + selector: "app-reseller-renewal-warning", + template: ` + @let warning = resellerRenewalWarning$ | async; + + @if (warning) { + + {{ warning.message }} + + } + `, + standalone: true, + imports: [AsyncPipe, BannerComponent], +}) +export class ResellerRenewalWarningComponent implements OnInit { + @Input({ required: true }) organization!: Organization; + + resellerRenewalWarning$!: Observable; + + constructor(private organizationWarningsService: OrganizationWarningsService) {} + + ngOnInit() { + this.resellerRenewalWarning$ = this.organizationWarningsService.getResellerRenewalWarning$( + this.organization, + ); + } +} 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 18ee9462f4f..dac5afa191a 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 @@ -183,7 +183,10 @@ describe("KeyRotationService", () => { mockKeyService.hashMasterKey.mockResolvedValue("mockMasterPasswordHash"); mockConfigService.getFeatureFlag.mockResolvedValue(true); - mockEncryptService.encrypt.mockResolvedValue({ + mockEncryptService.wrapSymmetricKey.mockResolvedValue({ + encryptedString: "mockEncryptedData", + } as any); + mockEncryptService.wrapDecapsulationKey.mockResolvedValue({ encryptedString: "mockEncryptedData", } as any); 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 7d00e970ad7..ec38f49baeb 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 @@ -145,7 +145,9 @@ export class UserKeyRotationService { const { privateKey, publicKey } = keyPair; const accountKeysRequest = new AccountKeysRequest( - (await this.encryptService.encrypt(privateKey, newUnencryptedUserKey)).encryptedString!, + ( + await this.encryptService.wrapDecapsulationKey(privateKey, newUnencryptedUserKey) + ).encryptedString!, Utils.fromBufferToB64(publicKey), ); @@ -427,6 +429,6 @@ export class UserKeyRotationService { if (privateKey == null) { throw new Error("No private key found for user key rotation"); } - return (await this.encryptService.encrypt(privateKey, newUserKey)).encryptedString; + return (await this.encryptService.wrapDecapsulationKey(privateKey, newUserKey)).encryptedString; } } diff --git a/apps/web/src/app/key-management/lock/services/web-lock-component.service.spec.ts b/apps/web/src/app/key-management/lock/services/web-lock-component.service.spec.ts index 3c941fe24c7..9e993259830 100644 --- a/apps/web/src/app/key-management/lock/services/web-lock-component.service.spec.ts +++ b/apps/web/src/app/key-management/lock/services/web-lock-component.service.spec.ts @@ -52,6 +52,22 @@ describe("WebLockComponentService", () => { }); }); + describe("popOutBrowserExtension", () => { + it("throws platform not supported error", () => { + expect(() => service.popOutBrowserExtension()).toThrow( + "Method not supported on this platform.", + ); + }); + }); + + describe("closeBrowserExtensionPopout", () => { + it("throws platform not supported error", () => { + expect(() => service.closeBrowserExtensionPopout()).toThrow( + "Method not supported on this platform.", + ); + }); + }); + describe("isWindowVisible", () => { it("throws an error", async () => { await expect(service.isWindowVisible()).rejects.toThrow("Method not implemented."); diff --git a/apps/web/src/app/key-management/lock/services/web-lock-component.service.ts b/apps/web/src/app/key-management/lock/services/web-lock-component.service.ts index dd9f5138dba..ea038ca2c67 100644 --- a/apps/web/src/app/key-management/lock/services/web-lock-component.service.ts +++ b/apps/web/src/app/key-management/lock/services/web-lock-component.service.ts @@ -24,6 +24,14 @@ export class WebLockComponentService implements LockComponentService { return null; } + popOutBrowserExtension(): Promise { + throw new Error("Method not supported on this platform."); + } + + closeBrowserExtensionPopout(): void { + throw new Error("Method not supported on this platform."); + } + async isWindowVisible(): Promise { throw new Error("Method not implemented."); } diff --git a/apps/web/src/app/shared/loose-components.module.ts b/apps/web/src/app/shared/loose-components.module.ts index eb63b9f798c..90e4c6ba9c3 100644 --- a/apps/web/src/app/shared/loose-components.module.ts +++ b/apps/web/src/app/shared/loose-components.module.ts @@ -56,15 +56,12 @@ import { UnsecuredWebsitesReportComponent as OrgUnsecuredWebsitesReportComponent import { WeakPasswordsReportComponent as OrgWeakPasswordsReportComponent } from "../tools/reports/pages/organizations/weak-passwords-report.component"; /* eslint no-restricted-imports: "error" */ import { PremiumBadgeComponent } from "../vault/components/premium-badge.component"; -import { AddEditCustomFieldsComponent } from "../vault/individual-vault/add-edit-custom-fields.component"; import { FolderAddEditComponent } from "../vault/individual-vault/folder-add-edit.component"; import { OrganizationBadgeModule } from "../vault/individual-vault/organization-badge/organization-badge.module"; import { PipesModule } from "../vault/individual-vault/pipes/pipes.module"; import { PurgeVaultComponent } from "../vault/settings/purge-vault.component"; import { FreeBitwardenFamiliesComponent } from "./../billing/members/free-bitwarden-families.component"; -import { OrganizationMemberFamiliesComponent } from "./../billing/members/organization-member-families.component"; -import { OrganizationSponsoredFamiliesComponent } from "./../billing/members/organization-sponsored-families.component"; import { EnvironmentSelectorModule } from "./../components/environment-selector/environment-selector.module"; import { AccountFingerprintComponent } from "./components/account-fingerprint/account-fingerprint.component"; import { SharedModule } from "./shared.module"; @@ -96,8 +93,6 @@ import { SharedModule } from "./shared.module"; declarations: [ AcceptFamilySponsorshipComponent, AccountComponent, - AddEditCustomFieldsComponent, - AddEditCustomFieldsComponent, ApiKeyComponent, ChangeEmailComponent, DeauthorizeSessionsComponent, @@ -131,8 +126,6 @@ import { SharedModule } from "./shared.module"; SelectableAvatarComponent, SetPasswordComponent, SponsoredFamiliesComponent, - OrganizationSponsoredFamiliesComponent, - OrganizationMemberFamiliesComponent, FreeBitwardenFamiliesComponent, SponsoringOrgRowComponent, UpdatePasswordComponent, @@ -144,8 +137,6 @@ import { SharedModule } from "./shared.module"; UserVerificationModule, PremiumBadgeComponent, AccountComponent, - AddEditCustomFieldsComponent, - AddEditCustomFieldsComponent, ApiKeyComponent, ChangeEmailComponent, DeauthorizeSessionsComponent, @@ -181,8 +172,6 @@ import { SharedModule } from "./shared.module"; SelectableAvatarComponent, SetPasswordComponent, SponsoredFamiliesComponent, - OrganizationSponsoredFamiliesComponent, - OrganizationMemberFamiliesComponent, FreeBitwardenFamiliesComponent, SponsoringOrgRowComponent, UpdateTempPasswordComponent, diff --git a/apps/web/src/app/tools/reports/pages/cipher-report.component.ts b/apps/web/src/app/tools/reports/pages/cipher-report.component.ts index 0ad8a0a519c..ceda7b1c480 100644 --- a/apps/web/src/app/tools/reports/pages/cipher-report.component.ts +++ b/apps/web/src/app/tools/reports/pages/cipher-report.component.ts @@ -67,7 +67,7 @@ export class CipherReportComponent implements OnDestroy { protected i18nService: I18nService, private syncService: SyncService, private cipherFormConfigService: CipherFormConfigService, - private adminConsoleCipherFormConfigService: AdminConsoleCipherFormConfigService, + protected adminConsoleCipherFormConfigService: AdminConsoleCipherFormConfigService, ) { this.organizations$ = this.accountService.activeAccount$.pipe( getUserId, @@ -207,7 +207,7 @@ export class CipherReportComponent implements OnDestroy { // If the dialog was closed by deleting the cipher, refresh the report. if (result === VaultItemDialogResult.Deleted || result === VaultItemDialogResult.Saved) { - await this.load(); + await this.refresh(result, cipher); } } @@ -215,6 +215,10 @@ export class CipherReportComponent implements OnDestroy { this.allCiphers = []; } + protected async refresh(result: VaultItemDialogResult, cipher: CipherView) { + await this.load(); + } + protected async repromptCipher(c: CipherView) { return ( c.reprompt === CipherRepromptType.None || diff --git a/apps/web/src/app/tools/reports/pages/weak-passwords-report.component.ts b/apps/web/src/app/tools/reports/pages/weak-passwords-report.component.ts index f7631e37a7d..4144c9ac20f 100644 --- a/apps/web/src/app/tools/reports/pages/weak-passwords-report.component.ts +++ b/apps/web/src/app/tools/reports/pages/weak-passwords-report.component.ts @@ -1,18 +1,22 @@ // FIXME: Update this file to be type safe and remove this and next line // @ts-strict-ignore import { Component, OnInit } from "@angular/core"; +import { firstValueFrom } from "rxjs"; import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction"; import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; +import { getUserId } from "@bitwarden/common/auth/services/account.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { Utils } from "@bitwarden/common/platform/misc/utils"; import { PasswordStrengthServiceAbstraction } from "@bitwarden/common/tools/password-strength"; +import { CipherId } from "@bitwarden/common/types/guid"; import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service"; import { SyncService } from "@bitwarden/common/vault/abstractions/sync/sync.service.abstraction"; import { CipherType } from "@bitwarden/common/vault/enums"; import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view"; import { BadgeVariant, DialogService } from "@bitwarden/components"; import { CipherFormConfigService, PasswordRepromptService } from "@bitwarden/vault"; +import { VaultItemDialogResult } from "@bitwarden/web-vault/app/vault/components/vault-item-dialog/vault-item-dialog.component"; import { AdminConsoleCipherFormConfigService } from "../../../vault/org-vault/services/admin-console-cipher-form-config.service"; @@ -40,7 +44,7 @@ export class WeakPasswordsReportComponent extends CipherReportComponent implemen i18nService: I18nService, syncService: SyncService, cipherFormConfigService: CipherFormConfigService, - adminConsoleCipherFormConfigService: AdminConsoleCipherFormConfigService, + protected adminConsoleCipherFormConfigService: AdminConsoleCipherFormConfigService, ) { super( cipherService, @@ -66,62 +70,112 @@ export class WeakPasswordsReportComponent extends CipherReportComponent implemen this.findWeakPasswords(allCiphers); } - protected findWeakPasswords(ciphers: CipherView[]): void { - ciphers.forEach((ciph) => { - const { type, login, isDeleted, edit, viewPassword } = ciph; - if ( - type !== CipherType.Login || - login.password == null || - login.password === "" || - isDeleted || - (!this.organization && !edit) || - !viewPassword - ) { + protected async refresh(result: VaultItemDialogResult, cipher: CipherView) { + if (result === VaultItemDialogResult.Deleted) { + // remove the cipher from the list + this.weakPasswordCiphers = this.weakPasswordCiphers.filter((c) => c.id !== cipher.id); + this.filterCiphersByOrg(this.weakPasswordCiphers); + return; + } + + if (result == VaultItemDialogResult.Saved) { + const activeUserId = await firstValueFrom(this.accountService.activeAccount$.pipe(getUserId)); + let updatedCipher = await this.cipherService.get(cipher.id, activeUserId); + + if (this.isAdminConsoleActive) { + updatedCipher = await this.adminConsoleCipherFormConfigService.getCipher( + cipher.id as CipherId, + this.organization, + ); + } + + const updatedCipherView = await updatedCipher.decrypt( + await this.cipherService.getKeyForCipherKeyDecryption(updatedCipher, activeUserId), + ); + // update the cipher views + const updatedReportResult = this.determineWeakPasswordScore(updatedCipherView); + const index = this.weakPasswordCiphers.findIndex((c) => c.id === updatedCipherView.id); + + if (updatedReportResult == null) { + // the password is no longer weak + // remove the cipher from the list + this.weakPasswordCiphers.splice(index, 1); + this.filterCiphersByOrg(this.weakPasswordCiphers); return; } - const hasUserName = this.isUserNameNotEmpty(ciph); - let userInput: string[] = []; - if (hasUserName) { - const atPosition = login.username.indexOf("@"); - if (atPosition > -1) { - userInput = userInput - .concat( - login.username - .substr(0, atPosition) - .trim() - .toLowerCase() - .split(/[^A-Za-z0-9]/), - ) - .filter((i) => i.length >= 3); - } else { - userInput = login.username - .trim() - .toLowerCase() - .split(/[^A-Za-z0-9]/) - .filter((i) => i.length >= 3); - } + if (index > -1) { + // update the existing cipher + this.weakPasswordCiphers[index] = updatedReportResult; + this.filterCiphersByOrg(this.weakPasswordCiphers); } - const result = this.passwordStrengthService.getPasswordStrength( - login.password, - null, - userInput.length > 0 ? userInput : null, - ); + } + } - if (result.score != null && result.score <= 2) { - const scoreValue = this.scoreKey(result.score); - const row = { - ...ciph, - score: result.score, - reportValue: scoreValue, - scoreKey: scoreValue.sortOrder, - } as ReportResult; + protected findWeakPasswords(ciphers: CipherView[]): void { + ciphers.forEach((ciph) => { + const row = this.determineWeakPasswordScore(ciph); + if (row != null) { this.weakPasswordCiphers.push(row); } }); this.filterCiphersByOrg(this.weakPasswordCiphers); } + 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 + ) { + return; + } + + const hasUserName = this.isUserNameNotEmpty(ciph); + let userInput: string[] = []; + if (hasUserName) { + const atPosition = login.username.indexOf("@"); + if (atPosition > -1) { + userInput = userInput + .concat( + login.username + .substr(0, atPosition) + .trim() + .toLowerCase() + .split(/[^A-Za-z0-9]/), + ) + .filter((i) => i.length >= 3); + } else { + userInput = login.username + .trim() + .toLowerCase() + .split(/[^A-Za-z0-9]/) + .filter((i) => i.length >= 3); + } + } + const result = this.passwordStrengthService.getPasswordStrength( + login.password, + null, + userInput.length > 0 ? userInput : null, + ); + + if (result.score != null && result.score <= 2) { + const scoreValue = this.scoreKey(result.score); + return { + ...ciph, + score: result.score, + reportValue: scoreValue, + scoreKey: scoreValue.sortOrder, + } as ReportResult; + } + + return null; + } + protected canManageCipher(c: CipherView): boolean { // this will only ever be false from the org view; return true; diff --git a/apps/web/src/app/vault/components/vault-item-dialog/vault-item-dialog.component.ts b/apps/web/src/app/vault/components/vault-item-dialog/vault-item-dialog.component.ts index 10466503029..d52f227fab5 100644 --- a/apps/web/src/app/vault/components/vault-item-dialog/vault-item-dialog.component.ts +++ b/apps/web/src/app/vault/components/vault-item-dialog/vault-item-dialog.component.ts @@ -7,6 +7,7 @@ import { firstValueFrom, Subject, switchMap } from "rxjs"; import { map } from "rxjs/operators"; import { CollectionView } from "@bitwarden/admin-console/common"; +import { VaultViewPasswordHistoryService } from "@bitwarden/angular/services/view-password-history.service"; import { ApiService } from "@bitwarden/common/abstractions/api.service"; import { EventCollectionService } from "@bitwarden/common/abstractions/event/event-collection.service"; import { Organization } from "@bitwarden/common/admin-console/models/domain/organization"; @@ -19,7 +20,7 @@ import { ConfigService } from "@bitwarden/common/platform/abstractions/config/co import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; import { MessagingService } from "@bitwarden/common/platform/abstractions/messaging.service"; -import { CipherId, CollectionId } from "@bitwarden/common/types/guid"; +import { CipherId, CollectionId, OrganizationId } from "@bitwarden/common/types/guid"; import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service"; import { PremiumUpgradePromptService } from "@bitwarden/common/vault/abstractions/premium-upgrade-prompt.service"; import { ViewPasswordHistoryService } from "@bitwarden/common/vault/abstractions/view-password-history.service"; @@ -39,6 +40,9 @@ import { ToastService, } from "@bitwarden/components"; import { + AttachmentDialogCloseResult, + AttachmentDialogResult, + AttachmentsV2Component, ChangeLoginPasswordService, CipherFormComponent, CipherFormConfig, @@ -50,16 +54,10 @@ import { } from "@bitwarden/vault"; import { SharedModule } from "../../../shared/shared.module"; -import { - AttachmentDialogCloseResult, - AttachmentDialogResult, - AttachmentsV2Component, -} from "../../individual-vault/attachments-v2.component"; +import { WebVaultPremiumUpgradePromptService } from "../../../vault/services/web-premium-upgrade-prompt.service"; import { RoutedVaultFilterService } from "../../individual-vault/vault-filter/services/routed-vault-filter.service"; import { RoutedVaultFilterModel } from "../../individual-vault/vault-filter/shared/models/routed-vault-filter.model"; import { WebCipherFormGenerationService } from "../../services/web-cipher-form-generation.service"; -import { WebVaultPremiumUpgradePromptService } from "../../services/web-premium-upgrade-prompt.service"; -import { WebViewPasswordHistoryService } from "../../services/web-view-password-history.service"; export type VaultItemDialogMode = "view" | "form"; @@ -135,7 +133,7 @@ export enum VaultItemDialogResult { ], providers: [ { provide: PremiumUpgradePromptService, useClass: WebVaultPremiumUpgradePromptService }, - { provide: ViewPasswordHistoryService, useClass: WebViewPasswordHistoryService }, + { provide: ViewPasswordHistoryService, useClass: VaultViewPasswordHistoryService }, { provide: CipherFormGenerationService, useClass: WebCipherFormGenerationService }, RoutedVaultFilterService, { provide: ChangeLoginPasswordService, useClass: DefaultChangeLoginPasswordService }, @@ -443,14 +441,15 @@ export class VaultItemDialogComponent implements OnInit, OnDestroy { return; } - const dialogRef = this.dialogService.open( - AttachmentsV2Component, - { - data: { - cipherId: this.formConfig.originalCipher?.id as CipherId, - }, + const dialogRef = this.dialogService.open< + AttachmentDialogCloseResult, + { cipherId: CipherId; organizationId?: OrganizationId } + >(AttachmentsV2Component, { + data: { + cipherId: this.formConfig.originalCipher?.id as CipherId, + organizationId: this.formConfig.originalCipher?.organizationId as OrganizationId, }, - ); + }); const result = await firstValueFrom(dialogRef.closed); diff --git a/apps/web/src/app/vault/individual-vault/add-edit-custom-fields.component.html b/apps/web/src/app/vault/individual-vault/add-edit-custom-fields.component.html deleted file mode 100644 index 759cc1b3b0f..00000000000 --- a/apps/web/src/app/vault/individual-vault/add-edit-custom-fields.component.html +++ /dev/null @@ -1,177 +0,0 @@ - - {{ "customFields" | i18n }} - - - - - {{ "name" | i18n }} - - - - - - - - {{ "value" | i18n }} - - - - - - - - - - - - - - - - - - - - - - - - - - - {{ o.name }} - - - - - - - - - - - - - - - - - - - {{ "newCustomField" | i18n }} - - - - {{ "type" | i18n }} - - {{ o.name }} - - {{ addFieldLinkedTypeOption.name }} - - - - - diff --git a/apps/web/src/app/vault/individual-vault/add-edit-custom-fields.component.ts b/apps/web/src/app/vault/individual-vault/add-edit-custom-fields.component.ts deleted file mode 100644 index b492de85caa..00000000000 --- a/apps/web/src/app/vault/individual-vault/add-edit-custom-fields.component.ts +++ /dev/null @@ -1,20 +0,0 @@ -// FIXME: Update this file to be type safe and remove this and next line -// @ts-strict-ignore -import { Component, Input } from "@angular/core"; - -import { AddEditCustomFieldsComponent as BaseAddEditCustomFieldsComponent } from "@bitwarden/angular/vault/components/add-edit-custom-fields.component"; -import { EventCollectionService } from "@bitwarden/common/abstractions/event/event-collection.service"; -import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; - -@Component({ - selector: "app-vault-add-edit-custom-fields", - templateUrl: "add-edit-custom-fields.component.html", -}) -export class AddEditCustomFieldsComponent extends BaseAddEditCustomFieldsComponent { - @Input() viewOnly: boolean; - @Input() copy: (value: string, typeI18nKey: string, aType: string) => void; - - constructor(i18nService: I18nService, eventCollectionService: EventCollectionService) { - super(i18nService, eventCollectionService); - } -} diff --git a/apps/web/src/app/vault/individual-vault/add-edit-v2.component.ts b/apps/web/src/app/vault/individual-vault/add-edit-v2.component.ts index d9b42594d79..5dcbf0d4e78 100644 --- a/apps/web/src/app/vault/individual-vault/add-edit-v2.component.ts +++ b/apps/web/src/app/vault/individual-vault/add-edit-v2.component.ts @@ -8,7 +8,7 @@ import { switchMap } from "rxjs"; import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; import { BillingAccountProfileStateService } from "@bitwarden/common/billing/abstractions/account/billing-account-profile-state.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; -import { CipherId } from "@bitwarden/common/types/guid"; +import { CipherId, OrganizationId } from "@bitwarden/common/types/guid"; import { CipherType } from "@bitwarden/common/vault/enums/cipher-type"; import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view"; import { @@ -21,6 +21,7 @@ import { ItemModule, } from "@bitwarden/components"; import { + AttachmentsV2Component, CipherAttachmentsComponent, CipherFormConfig, CipherFormGenerationService, @@ -31,8 +32,6 @@ import { import { SharedModule } from "../../shared/shared.module"; import { WebCipherFormGenerationService } from "../services/web-cipher-form-generation.service"; -import { AttachmentsV2Component } from "./attachments-v2.component"; - /** * The result of the AddEditCipherDialogV2 component. */ @@ -156,14 +155,15 @@ export class AddEditComponentV2 implements OnInit { * Opens the attachments dialog. */ async openAttachmentsDialog() { - this.dialogService.open( + this.dialogService.open< AttachmentsV2Component, - { - data: { - cipherId: this.config.originalCipher?.id as CipherId, - }, + { cipherId: CipherId; organizationId?: OrganizationId } + >(AttachmentsV2Component, { + data: { + cipherId: this.config.originalCipher?.id as CipherId, + organizationId: this.config.originalCipher?.organizationId as OrganizationId, }, - ); + }); } /** 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 6f281596bfb..ba0595a12c7 100644 --- a/apps/web/src/app/vault/individual-vault/vault.component.ts +++ b/apps/web/src/app/vault/individual-vault/vault.component.ts @@ -79,6 +79,9 @@ import { DialogRef, DialogService, Icons, ToastService } from "@bitwarden/compon import { AddEditFolderDialogComponent, AddEditFolderDialogResult, + AttachmentDialogCloseResult, + AttachmentDialogResult, + AttachmentsV2Component, CipherFormConfig, CollectionAssignmentResult, DecryptionFailureDialogComponent, @@ -86,7 +89,10 @@ import { PasswordRepromptService, } from "@bitwarden/vault"; -import { getNestedCollectionTree } from "../../admin-console/organizations/collections"; +import { + getNestedCollectionTree, + getFlatCollectionTree, +} from "../../admin-console/organizations/collections"; import { CollectionDialogAction, CollectionDialogTabType, @@ -106,11 +112,6 @@ import { VaultItem } from "../components/vault-items/vault-item"; import { VaultItemEvent } from "../components/vault-items/vault-item-event"; import { VaultItemsModule } from "../components/vault-items/vault-items.module"; -import { - AttachmentDialogCloseResult, - AttachmentDialogResult, - AttachmentsV2Component, -} from "./attachments-v2.component"; import { BulkDeleteDialogResult, openBulkDeleteDialog, @@ -391,31 +392,35 @@ export class VaultComponent implements OnInit, OnDestroy { if (filter.collectionId === undefined || filter.collectionId === Unassigned) { return []; } - let collectionsToReturn = []; + let searchableCollectionNodes: TreeNode[] = []; if (filter.organizationId !== undefined && filter.collectionId === All) { - collectionsToReturn = collections - .filter((c) => c.node.organizationId === filter.organizationId) - .map((c) => c.node); + searchableCollectionNodes = collections.filter( + (c) => c.node.organizationId === filter.organizationId, + ); } else if (filter.collectionId === All) { - collectionsToReturn = collections.map((c) => c.node); + searchableCollectionNodes = collections; } else { const selectedCollection = ServiceUtils.getTreeNodeObjectFromList( collections, filter.collectionId, ); - collectionsToReturn = selectedCollection?.children.map((c) => c.node) ?? []; + searchableCollectionNodes = selectedCollection?.children ?? []; } if (await this.searchService.isSearchable(activeUserId, searchText)) { - collectionsToReturn = this.searchPipe.transform( - collectionsToReturn, + // Flatten the tree for searching through all levels + const flatCollectionTree: CollectionView[] = + getFlatCollectionTree(searchableCollectionNodes); + + return this.searchPipe.transform( + flatCollectionTree, searchText, (collection) => collection.name, (collection) => collection.id, ); } - return collectionsToReturn; + return searchableCollectionNodes.map((treeNode: TreeNode) => treeNode.node); }), shareReplay({ refCount: true, bufferSize: 1 }), ); @@ -671,6 +676,7 @@ export class VaultComponent implements OnInit, OnDestroy { const dialogRef = AttachmentsV2Component.open(this.dialogService, { cipherId: cipher.id as CipherId, + organizationId: cipher.organizationId as OrganizationId, }); const result: AttachmentDialogCloseResult = await lastValueFrom(dialogRef.closed); diff --git a/apps/web/src/app/vault/individual-vault/view.component.ts b/apps/web/src/app/vault/individual-vault/view.component.ts index 3e5cced7fa8..e7b06cbb8d6 100644 --- a/apps/web/src/app/vault/individual-vault/view.component.ts +++ b/apps/web/src/app/vault/individual-vault/view.component.ts @@ -5,6 +5,7 @@ import { Component, EventEmitter, Inject, OnInit } from "@angular/core"; import { firstValueFrom, map, Observable } from "rxjs"; import { CollectionView } from "@bitwarden/admin-console/common"; +import { VaultViewPasswordHistoryService } from "@bitwarden/angular/services/view-password-history.service"; import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction"; import { Organization } from "@bitwarden/common/admin-console/models/domain/organization"; import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; @@ -21,8 +22,8 @@ import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view"; import { CipherAuthorizationService } from "@bitwarden/common/vault/services/cipher-authorization.service"; import { DIALOG_DATA, - DialogConfig, DialogRef, + DialogConfig, AsyncActionsModule, DialogModule, DialogService, @@ -31,8 +32,7 @@ import { import { CipherViewComponent } from "@bitwarden/vault"; import { SharedModule } from "../../shared/shared.module"; -import { WebVaultPremiumUpgradePromptService } from "../services/web-premium-upgrade-prompt.service"; -import { WebViewPasswordHistoryService } from "../services/web-view-password-history.service"; +import { WebVaultPremiumUpgradePromptService } from "../../vault/services/web-premium-upgrade-prompt.service"; export interface ViewCipherDialogParams { cipher: CipherView; @@ -74,7 +74,7 @@ export interface ViewCipherDialogCloseResult { standalone: true, imports: [CipherViewComponent, CommonModule, AsyncActionsModule, DialogModule, SharedModule], providers: [ - { provide: ViewPasswordHistoryService, useClass: WebViewPasswordHistoryService }, + { provide: ViewPasswordHistoryService, useClass: VaultViewPasswordHistoryService }, { provide: PremiumUpgradePromptService, useClass: WebVaultPremiumUpgradePromptService }, ], }) diff --git a/apps/web/src/app/vault/org-vault/services/admin-console-cipher-form-config.service.ts b/apps/web/src/app/vault/org-vault/services/admin-console-cipher-form-config.service.ts index dd9cef91a54..15af27ba8d0 100644 --- a/apps/web/src/app/vault/org-vault/services/admin-console-cipher-form-config.service.ts +++ b/apps/web/src/app/vault/org-vault/services/admin-console-cipher-form-config.service.ts @@ -100,7 +100,7 @@ export class AdminConsoleCipherFormConfigService implements CipherFormConfigServ }; } - private async getCipher(id: CipherId | null, organization: Organization): Promise { + async getCipher(id: CipherId | null, organization: Organization): Promise { if (id == null) { return null; } diff --git a/apps/web/src/connectors/sso.html b/apps/web/src/connectors/sso.html index 77996173cd7..55088c9f812 100644 --- a/apps/web/src/connectors/sso.html +++ b/apps/web/src/connectors/sso.html @@ -15,18 +15,16 @@ - - - - - - - - + + + + + + diff --git a/apps/web/src/connectors/sso.scss b/apps/web/src/connectors/sso.scss deleted file mode 100644 index a4c7f9b25b7..00000000000 --- a/apps/web/src/connectors/sso.scss +++ /dev/null @@ -1 +0,0 @@ -@import "../scss/styles.scss"; diff --git a/apps/web/src/connectors/sso.spec.ts b/apps/web/src/connectors/sso.spec.ts new file mode 100644 index 00000000000..45a41d94171 --- /dev/null +++ b/apps/web/src/connectors/sso.spec.ts @@ -0,0 +1,119 @@ +import { initiateWebAppSso, initiateBrowserSso } from "./sso"; + +describe("sso", () => { + let originalLocation: Location; + let originalPostMessage: any; + let postMessageSpy: jest.SpyInstance; + + beforeEach(() => { + // Save original window methods + originalLocation = window.location; + originalPostMessage = window.postMessage; + + // Mock location + Object.defineProperty(window, "location", { + value: { + href: "", + origin: "https://test.bitwarden.com", + }, + writable: true, + }); + + // Mock postMessage + postMessageSpy = jest.spyOn(window, "postMessage"); + + // Set up document + document.cookie = "ssoHandOffMessage=SSO login successful;SameSite=strict"; + const contentElement = document.createElement("div"); + contentElement.id = "content"; + document.body.appendChild(contentElement); + }); + + afterEach(() => { + // Restore original window methods + Object.defineProperty(window, "location", { value: originalLocation }); + window.postMessage = originalPostMessage; + + // Clean up document + const contentElement = document.getElementById("content"); + if (contentElement) { + document.body.removeChild(contentElement); + } + document.cookie = "ssoHandOffMessage=;SameSite=strict;max-age=0"; + + // Clear mocks + jest.clearAllMocks(); + }); + + describe("initiateWebAppSso", () => { + it("redirects to the SSO component with code and state", () => { + const code = "testcode"; + const state = "teststate"; + + initiateWebAppSso(code, state); + + expect(window.location.href).toBe( + "https://test.bitwarden.com/#/sso?code=testcode&state=teststate", + ); + }); + + it("redirects to the return URI when included in state", () => { + const code = "testcode"; + const state = "teststate_returnUri='/organizations'"; + + initiateWebAppSso(code, state); + + expect(window.location.href).toBe("https://test.bitwarden.com/#/organizations"); + }); + + it("handles empty code parameter", () => { + initiateWebAppSso("", "teststate"); + expect(window.location.href).toBe("https://test.bitwarden.com/#/sso?code=&state=teststate"); + }); + + it("handles empty state parameter", () => { + initiateWebAppSso("testcode", ""); + expect(window.location.href).toBe("https://test.bitwarden.com/#/sso?code=testcode&state="); + }); + }); + + describe("initiateBrowserSso", () => { + it("posts message with code and state", () => { + const code = "testcode"; + const state = "teststate"; + const lastpass = false; + + initiateBrowserSso(code, state, lastpass); + + expect(postMessageSpy).toHaveBeenCalledWith( + { command: "authResult", code, state, lastpass }, + window.location.origin, + ); + }); + + it("updates content with message from cookie", () => { + const code = "testcode"; + const state = "teststate"; + const lastpass = false; + + initiateBrowserSso(code, state, lastpass); + + const contentElement = document.getElementById("content"); + const paragraphElement = contentElement?.querySelector("p"); + expect(paragraphElement?.innerText).toBe("SSO login successful"); + }); + + it("handles lastpass flag correctly", () => { + const code = "testcode"; + const state = "teststate"; + const lastpass = true; + + initiateBrowserSso(code, state, lastpass); + + expect(postMessageSpy).toHaveBeenCalledWith( + { command: "authResult", code, state, lastpass }, + window.location.origin, + ); + }); + }); +}); diff --git a/apps/web/src/connectors/sso.ts b/apps/web/src/connectors/sso.ts index 886742c4c49..55d661b35e8 100644 --- a/apps/web/src/connectors/sso.ts +++ b/apps/web/src/connectors/sso.ts @@ -2,10 +2,6 @@ // @ts-strict-ignore import { getQsParam } from "./common"; -// FIXME: Remove when updating file. Eslint update -// eslint-disable-next-line @typescript-eslint/no-require-imports -require("./sso.scss"); - window.addEventListener("load", () => { const code = getQsParam("code"); const state = getQsParam("state"); @@ -20,7 +16,7 @@ window.addEventListener("load", () => { } }); -function initiateWebAppSso(code: string, state: string) { +export function initiateWebAppSso(code: string, state: string) { // If we've initiated SSO from somewhere other than the SSO component on the web app, the SSO component will add // a _returnUri to the state variable. Here we're extracting that URI and sending the user there instead of to the SSO component. const returnUri = extractFromRegex(state, "(?<=_returnUri=')(.*)(?=')"); @@ -31,7 +27,7 @@ function initiateWebAppSso(code: string, state: string) { } } -function initiateBrowserSso(code: string, state: string, lastpass: boolean) { +export function initiateBrowserSso(code: string, state: string, lastpass: boolean) { window.postMessage({ command: "authResult", code, state, lastpass }, window.location.origin); const handOffMessage = ("; " + document.cookie) .split("; ssoHandOffMessage=") diff --git a/apps/web/src/connectors/webauthn-fallback.ts b/apps/web/src/connectors/webauthn-fallback.ts index 3561f922e03..43be5733973 100644 --- a/apps/web/src/connectors/webauthn-fallback.ts +++ b/apps/web/src/connectors/webauthn-fallback.ts @@ -86,7 +86,7 @@ document.addEventListener("DOMContentLoaded", async () => { titleForLargerScreens.innerText = localeService.t("verifyYourIdentity"); const subtitle = document.getElementById("subtitle"); - subtitle.innerText = localeService.t("followTheStepsBelowToFinishLoggingIn"); + subtitle.innerText = localeService.t("followTheStepsBelowToFinishLoggingInWithSecurityKey"); }); function start() { diff --git a/apps/web/src/locales/af/messages.json b/apps/web/src/locales/af/messages.json index 56eabc34863..d443649b588 100644 --- a/apps/web/src/locales/af/messages.json +++ b/apps/web/src/locales/af/messages.json @@ -2,6 +2,9 @@ "allApplications": { "message": "All applications" }, + "appLogoLabel": { + "message": "Bitwarden logo" + }, "criticalApplications": { "message": "Critical applications" }, @@ -419,18 +422,9 @@ "folder": { "message": "Vouer" }, - "newCustomField": { - "message": "Nuwe pasgemaakte veld" - }, "value": { "message": "Waarde" }, - "dragToSort": { - "message": "Sleep om te sorteer" - }, - "dragToReorder": { - "message": "Drag to reorder" - }, "cfTypeText": { "message": "Teks" }, @@ -4072,8 +4066,8 @@ "updateBrowser": { "message": "Werk Blaaier By" }, - "generatingRiskInsights": { - "message": "Generating your risk insights..." + "generatingYourRiskInsights": { + "message": "Generating your Risk Insights..." }, "updateBrowserDesc": { "message": "U gebruik ’n onondersteunde webblaaier. Die webkluis werk dalk nie soos normaal nie." @@ -6305,6 +6299,21 @@ "sponsoredFamilies": { "message": "Gratis Bitwarden Gesinne" }, + "sponsoredBitwardenFamilies": { + "message": "Sponsored families" + }, + "noSponsoredFamilies": { + "message": "No sponsored families" + }, + "noSponsoredFamiliesDescription": { + "message": "Sponsored non-member families plans will display here" + }, + "sponsorFreeBitwardenFamilies": { + "message": "Members of your organization are eligible for Free Bitwarden Families. You can sponsor Free Bitwarden Families for employees who are not a member of your Bitwarden organization. Sponsoring a non-member requires an available seat within your organization." + }, + "sponsoredFamiliesRemoveActiveSponsorship": { + "message": "When you remove an active sponsorship, a seat within your organization will be available after the renewal date of the sponsored organization." + }, "sponsoredFamiliesEligible": { "message": "U en u gesin kom in aanmerking vir gratis Bitwarden Gesinne. Los af met u persoonlike e-pos om u data veilig te hou selfs wanneer u nie op kantoor is nie." }, @@ -6320,6 +6329,18 @@ "sponsoredFamiliesSharedCollections": { "message": "Gedeelde versamelings vir gesinsgeheime" }, + "memberFamilies": { + "message": "Member families" + }, + "noMemberFamilies": { + "message": "No member families" + }, + "noMemberFamiliesDescription": { + "message": "Members who have redeemed family plans will display here" + }, + "membersWithSponsoredFamilies": { + "message": "Members of your organization are eligible for Free Bitwarden Families. Here you can see members who have sponsored a Families organization." + }, "badToken": { "message": "Hierdie skakel is nie meer geldig nie. Laat die borg die aanbod weer stuur." }, @@ -7249,6 +7270,9 @@ "followTheStepsBelowToFinishLoggingIn": { "message": "Follow the steps below to finish logging in." }, + "followTheStepsBelowToFinishLoggingInWithSecurityKey": { + "message": "Follow the steps below to finish logging in with your security key." + }, "launchDuo": { "message": "Launch Duo" }, @@ -7983,6 +8007,9 @@ "inviteMember": { "message": "Nooi lid uit" }, + "addSponsorship": { + "message": "Add sponsorship" + }, "needsConfirmation": { "message": "Moet bevestig word" }, @@ -10571,20 +10598,11 @@ "removeUnlockWithPinPolicyDesc": { "message": "Do not allow members to unlock their account with a PIN." }, - "limitedEventLogs": { - "message": "$PRODUCT_TYPE$ plans do not have access to real event logs", - "placeholders": { - "product_type": { - "content": "$1", - "example": "Teams" - } - } + "upgradeForFullEventsMessage": { + "message": "Event logs are not stored for your organization. Upgrade to a Teams or Enterprise plan to get full access to organization event logs." }, - "upgradeForFullEvents": { - "message": "Get full access to organization event logs by upgrading to a Teams or Enterprise plan." - }, - "upgradeEventLogTitle": { - "message": "Upgrade for real event log data" + "upgradeEventLogTitleMessage": { + "message": "Upgrade to see event logs from your organization." }, "upgradeEventLogMessage": { "message": "These events are examples only and do not reflect real events within your Bitwarden organization." @@ -10600,5 +10618,8 @@ }, "newBusinessUnit": { "message": "New business unit" + }, + "restart": { + "message": "Restart" } } diff --git a/apps/web/src/locales/ar/messages.json b/apps/web/src/locales/ar/messages.json index 9ebd9873655..e2d8a1e254c 100644 --- a/apps/web/src/locales/ar/messages.json +++ b/apps/web/src/locales/ar/messages.json @@ -2,6 +2,9 @@ "allApplications": { "message": "كل التطبيقات" }, + "appLogoLabel": { + "message": "Bitwarden logo" + }, "criticalApplications": { "message": "Critical applications" }, @@ -419,18 +422,9 @@ "folder": { "message": "المجلد" }, - "newCustomField": { - "message": "حقل مخصص جديد" - }, "value": { "message": "القيمة" }, - "dragToSort": { - "message": "اسحب للفرز" - }, - "dragToReorder": { - "message": "Drag to reorder" - }, "cfTypeText": { "message": "نص" }, @@ -4072,8 +4066,8 @@ "updateBrowser": { "message": "Update browser" }, - "generatingRiskInsights": { - "message": "Generating your risk insights..." + "generatingYourRiskInsights": { + "message": "Generating your Risk Insights..." }, "updateBrowserDesc": { "message": "You are using an unsupported web browser. The web vault may not function properly." @@ -6305,6 +6299,21 @@ "sponsoredFamilies": { "message": "Free Bitwarden Families" }, + "sponsoredBitwardenFamilies": { + "message": "Sponsored families" + }, + "noSponsoredFamilies": { + "message": "No sponsored families" + }, + "noSponsoredFamiliesDescription": { + "message": "Sponsored non-member families plans will display here" + }, + "sponsorFreeBitwardenFamilies": { + "message": "Members of your organization are eligible for Free Bitwarden Families. You can sponsor Free Bitwarden Families for employees who are not a member of your Bitwarden organization. Sponsoring a non-member requires an available seat within your organization." + }, + "sponsoredFamiliesRemoveActiveSponsorship": { + "message": "When you remove an active sponsorship, a seat within your organization will be available after the renewal date of the sponsored organization." + }, "sponsoredFamiliesEligible": { "message": "You and your family are eligible for Free Bitwarden Families. Redeem with your personal email to keep your data secure even when you are not at work." }, @@ -6320,6 +6329,18 @@ "sponsoredFamiliesSharedCollections": { "message": "Shared collections for Family secrets" }, + "memberFamilies": { + "message": "Member families" + }, + "noMemberFamilies": { + "message": "No member families" + }, + "noMemberFamiliesDescription": { + "message": "Members who have redeemed family plans will display here" + }, + "membersWithSponsoredFamilies": { + "message": "Members of your organization are eligible for Free Bitwarden Families. Here you can see members who have sponsored a Families organization." + }, "badToken": { "message": "The link is no longer valid. Please have the sponsor resend the offer." }, @@ -7249,6 +7270,9 @@ "followTheStepsBelowToFinishLoggingIn": { "message": "Follow the steps below to finish logging in." }, + "followTheStepsBelowToFinishLoggingInWithSecurityKey": { + "message": "Follow the steps below to finish logging in with your security key." + }, "launchDuo": { "message": "Launch Duo" }, @@ -7983,6 +8007,9 @@ "inviteMember": { "message": "Invite member" }, + "addSponsorship": { + "message": "Add sponsorship" + }, "needsConfirmation": { "message": "Needs confirmation" }, @@ -10571,20 +10598,11 @@ "removeUnlockWithPinPolicyDesc": { "message": "Do not allow members to unlock their account with a PIN." }, - "limitedEventLogs": { - "message": "$PRODUCT_TYPE$ plans do not have access to real event logs", - "placeholders": { - "product_type": { - "content": "$1", - "example": "Teams" - } - } + "upgradeForFullEventsMessage": { + "message": "Event logs are not stored for your organization. Upgrade to a Teams or Enterprise plan to get full access to organization event logs." }, - "upgradeForFullEvents": { - "message": "Get full access to organization event logs by upgrading to a Teams or Enterprise plan." - }, - "upgradeEventLogTitle": { - "message": "Upgrade for real event log data" + "upgradeEventLogTitleMessage": { + "message": "Upgrade to see event logs from your organization." }, "upgradeEventLogMessage": { "message": "These events are examples only and do not reflect real events within your Bitwarden organization." @@ -10600,5 +10618,8 @@ }, "newBusinessUnit": { "message": "New business unit" + }, + "restart": { + "message": "Restart" } } diff --git a/apps/web/src/locales/az/messages.json b/apps/web/src/locales/az/messages.json index 78b0b1ec825..e3be53ee7bb 100644 --- a/apps/web/src/locales/az/messages.json +++ b/apps/web/src/locales/az/messages.json @@ -2,6 +2,9 @@ "allApplications": { "message": "Bütün tətbiqlər" }, + "appLogoLabel": { + "message": "Bitwarden loqosu" + }, "criticalApplications": { "message": "Kritik tətbiqlər" }, @@ -419,18 +422,9 @@ "folder": { "message": "Qovluq" }, - "newCustomField": { - "message": "Yeni özəl xana" - }, "value": { "message": "Dəyər" }, - "dragToSort": { - "message": "Sıralamaq üçün sürüklə" - }, - "dragToReorder": { - "message": "Yenidən sıralamaq üçün sürüklə" - }, "cfTypeText": { "message": "Mətn" }, @@ -3315,13 +3309,13 @@ "message": "Bir üzv ləğv edildikdə, artıq həmin üzv təşkilat datasına müraciət edə bilmir. Üzv müraciətini daha tez bərpa etmək üçün, Ləğv edilənlər vərəqinə gedin." }, "removeUserConfirmationKeyConnector": { - "message": "Xəbərdarlıq! Bu istifadəçi, şifrələmələrini idarə etmək üçün Açar Bağlayıcı tələb edir. Bu istifadəçini təşkilatınızdan silsəniz, hesabı birdəfəlik sıradan çıxarılacaq. Bu əməliyyatın geri dönüşü yoxdur. Davam etmək istəyirsiniz?" + "message": "Xəbərdarlıq! Bu istifadəçi, şifrələmələrini idarə etmək üçün Key Connector tələb edir. Bu istifadəçini təşkilatınızdan silsəniz, hesabı birdəfəlik sıradan çıxarılacaq. Bu əməliyyatın geri dönüşü yoxdur. Davam etmək istəyirsiniz?" }, "externalId": { "message": "Xarici kimlik" }, "externalIdDesc": { - "message": "Xarici kimlik, referans olaraq və ya bu mənbəni istifadəçi kataloqu kimi xarici bir sistemə bağlamaq üçün istifadə edilir." + "message": "Xarici kimlik, Bitwarden Directory Connector və API tərəfindən istifadə edilən şifrələnməmiş istinaddır." }, "ssoExternalId": { "message": "SSO Xarici Kimlik" @@ -4072,8 +4066,8 @@ "updateBrowser": { "message": "Brauzeri güncəllə" }, - "generatingRiskInsights": { - "message": "Risk təhlilləriniz yaradılır..." + "generatingYourRiskInsights": { + "message": "Risk Təhlilləriniz yaradılır..." }, "updateBrowserDesc": { "message": "Dəstəklənməyən bir veb brauzer istifadə edirsiniz. Veb seyf düzgün işləməyə bilər." @@ -6305,6 +6299,21 @@ "sponsoredFamilies": { "message": "Ödənişsiz Bitwarden Ailələri" }, + "sponsoredBitwardenFamilies": { + "message": "Sponsorlu ailələr" + }, + "noSponsoredFamilies": { + "message": "Sponsorlu ailələr yoxdur" + }, + "noSponsoredFamiliesDescription": { + "message": "Sponsorlu üzv olmayan ailələr planı burada görünəcək" + }, + "sponsorFreeBitwardenFamilies": { + "message": "Təşkilatınızın üzvləri Ödənişsiz Bitwarden Ailələr planından yararlana bilər. Bitwarden təşkilatınızın üzvü olmayan işçilər üçün \"Ödənişsiz Bitwarden Ailələr\"ə sponsor ola bilərsiniz. Üzv olmayan birinə sponsorluq etmək üçün təşkilatınızda mövcud yer olmalıdır." + }, + "sponsoredFamiliesRemoveActiveSponsorship": { + "message": "Aktiv bir sponsorluğu ləğv etdiyiniz zaman, sponsorlu təşkilatınızın yenilənmə tarixindən sonra təşkilatınızda bir yer mövcud olacaq." + }, "sponsoredFamiliesEligible": { "message": "Siz və ailəniz Ödənişsiz Bitwarden Ailələri üçün uyğunsunuz. İşdə olmadığınız vaxtlarda belə datanızı güvənli saxlamaq üçün özəl e-poçtunuzu istifadə edin." }, @@ -6320,6 +6329,18 @@ "sponsoredFamiliesSharedCollections": { "message": "Ailə sirləri üçün paylaşılan kolleksiyalar" }, + "memberFamilies": { + "message": "Üzv ailələr" + }, + "noMemberFamilies": { + "message": "Üzv olan ailə yoxdur" + }, + "noMemberFamiliesDescription": { + "message": "Ailə planlarını götürən üzvlər burada görünəcək" + }, + "membersWithSponsoredFamilies": { + "message": "Təşkilatınızın üzvləri Ödənişsiz Bitwarden Ailələr planından yararlana bilər. Burada Ailələr təşkilatına sponsorluq edən üzvləri görə bilərsiniz." + }, "badToken": { "message": "Keçid, artıq etibarlı deyil. Lütfən sponsorun təklifi yenidən göndərməsini təmin edin." }, @@ -6414,10 +6435,10 @@ "message": "Sponsorluq silindi" }, "ssoKeyConnectorError": { - "message": "Açar bağlayıcı xətası: Açar Bağlayıcının mövcud olduğuna və düzgün işlədiyinə əmin olun." + "message": "Key Connector xətası: \"Key Connector\"un mövcud olduğuna və düzgün işlədiyinə əmin olun." }, "keyConnectorUrl": { - "message": "Açar Bağlayıcı URL-si" + "message": "Key Connector URL-si" }, "sendVerificationCode": { "message": "Doğrulama kodunu e-poçtunuza göndərin" @@ -6486,7 +6507,7 @@ "message": "Kimlik doğrulandıqdan sonra üzvlər, ana parollarını istifadə edərək seyf datalarının şifrəsini aça biləcək." }, "keyConnector": { - "message": "Açar Bağlayıcı" + "message": "Key Connector" }, "memberDecryptionKeyConnectorDescStart": { "message": "SSO ilə Girişi, self-hosted şifrə açma açar serverinizə bağlayın. Bu seçimi istifadə edərək, üzvlərin seyf datasının şifrəsini açmaq üçün ana parollarını istifadə etməsinə ehtiyac qalmayacaq.", @@ -6497,11 +6518,11 @@ "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Connect login with SSO to your self-hosted decryption key server. Using this option, members won’t need to use their master passwords to decrypt vault data. The require SSO authentication and single organization policies are required to set up Key Connector decryption. Contact Bitwarden Support for set up assistance.'" }, "memberDecryptionKeyConnectorDescEnd": { - "message": "Açar Bağlayıcı şifrə açmanı quraşdırmaq üçün tələb olunur. Quraşdırma üzrə kömək üçün Bitwarden Dəstək ilə əlaqə saxlayın.", + "message": "Key Connector şifrə açmanı quraşdırmaq üçün tələb olunur. Quraşdırma üzrə kömək üçün Bitwarden Dəstək ilə əlaqə saxlayın.", "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Connect login with SSO to your self-hosted decryption key server. Using this option, members won’t need to use their master passwords to decrypt vault data. The require SSO authentication and single organization policies are required to set up Key Connector decryption. Contact Bitwarden Support for set up assistance.'" }, "keyConnectorPolicyRestriction": { - "message": "\"SSO və Açar Bağlayıcı Şifrə Açma ilə Giriş\" fəaldır. Bu siyasət yalnız Sahiblər və Adminlər üçün etibarlıdır." + "message": "\"SSO və Key Connector Şifrə Açma ilə Giriş\" fəaldır. Bu siyasət yalnız sahiblər və adminlər üçün etibarlıdır." }, "enabledSso": { "message": "SSO fəaldır" @@ -6510,16 +6531,16 @@ "message": "SSO sıradan çıxarılıb" }, "enabledKeyConnector": { - "message": "Açar Bağlayıcı fəaldır" + "message": "Key Connector aktivdir" }, "disabledKeyConnector": { - "message": "Açar Bağlayıcı sıradan çıxarılıb" + "message": "Key Connector deaktivdir" }, "keyConnectorWarning": { - "message": "Açar Bağlayıcı qurulduqdan sonra, Üzv şifrə açma seçimləri dəyişdirilə bilməz." + "message": "Üzvlər \"Key Connector\"u istifadə etməyə başladıqdan sonra, təşkilatınız ana parol ilə şifrə açma proseduruna geri qaytarıla bilməz. Yalnız bir açar serverini deploy və idarə etmə üçün uyğun olduqda davam edin." }, "migratedKeyConnector": { - "message": "Açar Bağlayıcıya daşındı" + "message": "\"Key Connector\"a daşındı" }, "paymentSponsored": { "message": "Lütfən təşkilatla əlaqələndirmək üçün bir ödəniş metodu təqdim edin. Narahat olmayın, əlavə özəllikləri seçmədiyiniz və ya sponsorluğunuz bitmədiyi müddətcə sizdən heç bir ödəniş tutulmayacaq. " @@ -6540,10 +6561,10 @@ "message": "Test" }, "keyConnectorTestSuccess": { - "message": "Uğurlu! Açar Bağlayıcı əlçatandır." + "message": "Uğurlu! Key Connector əlçatandır." }, "keyConnectorTestFail": { - "message": "Açar Bağlayıcıya müraciət edilə bilmir. URL-ni yoxlayın." + "message": "\"Key Connector\"a müraciət edilə bilmir. URL-ni yoxlayın." }, "sponsorshipTokenHasExpired": { "message": "Sponsorluq təklifinin müddəti bitdi." @@ -7249,6 +7270,9 @@ "followTheStepsBelowToFinishLoggingIn": { "message": "Giriş prosesini tamamlamaq üçün aşağıdakı addımları izləyin." }, + "followTheStepsBelowToFinishLoggingInWithSecurityKey": { + "message": "Güvənlik açarınızla girişi tamamlamaq üçün aşağıdakı addımları izləyin." + }, "launchDuo": { "message": "DUO-nu başlat" }, @@ -7983,6 +8007,9 @@ "inviteMember": { "message": "Üzv dəvət et" }, + "addSponsorship": { + "message": "Sponsorluq əlavə et" + }, "needsConfirmation": { "message": "Təsdiqləmə lazımdır" }, @@ -10571,20 +10598,11 @@ "removeUnlockWithPinPolicyDesc": { "message": "Üzvlərin öz hesablarının kilidini PIN ilə açmasına icazə verilməsin." }, - "limitedEventLogs": { - "message": "$PRODUCT_TYPE$ planında real event log-larına müraciət yoxdur", - "placeholders": { - "product_type": { - "content": "$1", - "example": "Teams" - } - } + "upgradeForFullEventsMessage": { + "message": "Event logs are not stored for your organization. Upgrade to a Teams or Enterprise plan to get full access to organization event logs." }, - "upgradeForFullEvents": { - "message": "Teams və ya Enterprise planına yüksəldərək təşkilatın event log-larına tam müraciət əldə edin." - }, - "upgradeEventLogTitle": { - "message": "Real event log dataları üçün yüksəlt" + "upgradeEventLogTitleMessage": { + "message": "Upgrade to see event logs from your organization." }, "upgradeEventLogMessage": { "message": "Bu event-lər sadəcə nümunədir və Bitwarden təşkilatınızdakı real event-ləri əks etdirmir." @@ -10600,5 +10618,8 @@ }, "newBusinessUnit": { "message": "Yeni biznes vahidi" + }, + "restart": { + "message": "Restart" } } diff --git a/apps/web/src/locales/be/messages.json b/apps/web/src/locales/be/messages.json index 0b0b405721f..b279f4ac0c9 100644 --- a/apps/web/src/locales/be/messages.json +++ b/apps/web/src/locales/be/messages.json @@ -2,6 +2,9 @@ "allApplications": { "message": "Усе праграмы" }, + "appLogoLabel": { + "message": "Bitwarden logo" + }, "criticalApplications": { "message": "Крытычныя праграмы" }, @@ -419,18 +422,9 @@ "folder": { "message": "Папка" }, - "newCustomField": { - "message": "Новае карыстальніцкае поле" - }, "value": { "message": "Значэнне" }, - "dragToSort": { - "message": "Перацягніце для сартавання" - }, - "dragToReorder": { - "message": "Drag to reorder" - }, "cfTypeText": { "message": "Тэкст" }, @@ -4072,8 +4066,8 @@ "updateBrowser": { "message": "Абнавіць браўзер" }, - "generatingRiskInsights": { - "message": "Generating your risk insights..." + "generatingYourRiskInsights": { + "message": "Generating your Risk Insights..." }, "updateBrowserDesc": { "message": "Ваш браўзер не падтрымліваецца. Вэб-сховішча можа працаваць няправільна." @@ -6305,6 +6299,21 @@ "sponsoredFamilies": { "message": "Бясплатны тарыфны план Bitwarden Families" }, + "sponsoredBitwardenFamilies": { + "message": "Sponsored families" + }, + "noSponsoredFamilies": { + "message": "No sponsored families" + }, + "noSponsoredFamiliesDescription": { + "message": "Sponsored non-member families plans will display here" + }, + "sponsorFreeBitwardenFamilies": { + "message": "Members of your organization are eligible for Free Bitwarden Families. You can sponsor Free Bitwarden Families for employees who are not a member of your Bitwarden organization. Sponsoring a non-member requires an available seat within your organization." + }, + "sponsoredFamiliesRemoveActiveSponsorship": { + "message": "When you remove an active sponsorship, a seat within your organization will be available after the renewal date of the sponsored organization." + }, "sponsoredFamiliesEligible": { "message": "Вы і ваша сям'я маеце права на бясплатны тарыфны план Bitwarden Families. Актывуйце доступ з асабістай электроннай поштай, каб захаваць свае даныя ў бяспецы, нават калі вы не на працы." }, @@ -6320,6 +6329,18 @@ "sponsoredFamiliesSharedCollections": { "message": "Абагуленыя калекцыі для сямейных сакрэтаў" }, + "memberFamilies": { + "message": "Member families" + }, + "noMemberFamilies": { + "message": "No member families" + }, + "noMemberFamiliesDescription": { + "message": "Members who have redeemed family plans will display here" + }, + "membersWithSponsoredFamilies": { + "message": "Members of your organization are eligible for Free Bitwarden Families. Here you can see members who have sponsored a Families organization." + }, "badToken": { "message": "Спасылка больш не дзейнічае. Калі ласка, папрасіце спонсара паўторна адправіць прапанову." }, @@ -7249,6 +7270,9 @@ "followTheStepsBelowToFinishLoggingIn": { "message": "Follow the steps below to finish logging in." }, + "followTheStepsBelowToFinishLoggingInWithSecurityKey": { + "message": "Follow the steps below to finish logging in with your security key." + }, "launchDuo": { "message": "Launch Duo" }, @@ -7983,6 +8007,9 @@ "inviteMember": { "message": "Запрасіць удзельніка" }, + "addSponsorship": { + "message": "Add sponsorship" + }, "needsConfirmation": { "message": "Патрабуецца пацвярджэнне" }, @@ -10571,20 +10598,11 @@ "removeUnlockWithPinPolicyDesc": { "message": "Do not allow members to unlock their account with a PIN." }, - "limitedEventLogs": { - "message": "$PRODUCT_TYPE$ plans do not have access to real event logs", - "placeholders": { - "product_type": { - "content": "$1", - "example": "Teams" - } - } + "upgradeForFullEventsMessage": { + "message": "Event logs are not stored for your organization. Upgrade to a Teams or Enterprise plan to get full access to organization event logs." }, - "upgradeForFullEvents": { - "message": "Get full access to organization event logs by upgrading to a Teams or Enterprise plan." - }, - "upgradeEventLogTitle": { - "message": "Upgrade for real event log data" + "upgradeEventLogTitleMessage": { + "message": "Upgrade to see event logs from your organization." }, "upgradeEventLogMessage": { "message": "These events are examples only and do not reflect real events within your Bitwarden organization." @@ -10600,5 +10618,8 @@ }, "newBusinessUnit": { "message": "New business unit" + }, + "restart": { + "message": "Restart" } } diff --git a/apps/web/src/locales/bg/messages.json b/apps/web/src/locales/bg/messages.json index 13bbdc14d4a..e31c5a6da13 100644 --- a/apps/web/src/locales/bg/messages.json +++ b/apps/web/src/locales/bg/messages.json @@ -2,6 +2,9 @@ "allApplications": { "message": "Всички приложения" }, + "appLogoLabel": { + "message": "Лого на Битуорден" + }, "criticalApplications": { "message": "Важни приложения" }, @@ -419,18 +422,9 @@ "folder": { "message": "Папка" }, - "newCustomField": { - "message": "Ново допълнително поле" - }, "value": { "message": "Стойност" }, - "dragToSort": { - "message": "Подредба чрез влачене" - }, - "dragToReorder": { - "message": "Плъзнете за пренареждане" - }, "cfTypeText": { "message": "Текст" }, @@ -4072,7 +4066,7 @@ "updateBrowser": { "message": "Обновяване на браузъра" }, - "generatingRiskInsights": { + "generatingYourRiskInsights": { "message": "Създаване на Вашата информация относно рисковете…" }, "updateBrowserDesc": { @@ -6305,6 +6299,21 @@ "sponsoredFamilies": { "message": "Free Bitwarden Families" }, + "sponsoredBitwardenFamilies": { + "message": "Sponsored families" + }, + "noSponsoredFamilies": { + "message": "No sponsored families" + }, + "noSponsoredFamiliesDescription": { + "message": "Sponsored non-member families plans will display here" + }, + "sponsorFreeBitwardenFamilies": { + "message": "Members of your organization are eligible for Free Bitwarden Families. You can sponsor Free Bitwarden Families for employees who are not a member of your Bitwarden organization. Sponsoring a non-member requires an available seat within your organization." + }, + "sponsoredFamiliesRemoveActiveSponsorship": { + "message": "When you remove an active sponsorship, a seat within your organization will be available after the renewal date of the sponsored organization." + }, "sponsoredFamiliesEligible": { "message": "You and your family are eligible for Free Bitwarden Families. Redeem with your personal email to keep your data secure even when you are not at work." }, @@ -6320,6 +6329,18 @@ "sponsoredFamiliesSharedCollections": { "message": "Споделени колекции за семейни тайни" }, + "memberFamilies": { + "message": "Member families" + }, + "noMemberFamilies": { + "message": "No member families" + }, + "noMemberFamiliesDescription": { + "message": "Members who have redeemed family plans will display here" + }, + "membersWithSponsoredFamilies": { + "message": "Members of your organization are eligible for Free Bitwarden Families. Here you can see members who have sponsored a Families organization." + }, "badToken": { "message": "Връзката е с изтекла давност. Помолете спонсора да изпрати отново предложението." }, @@ -7249,6 +7270,9 @@ "followTheStepsBelowToFinishLoggingIn": { "message": "Следвайте стъпките по-долу, за да завършите вписването." }, + "followTheStepsBelowToFinishLoggingInWithSecurityKey": { + "message": "Следвайте стъпките по-долу, за да завършите вписването си чрез устройството за удостоверяване." + }, "launchDuo": { "message": "Стартиране на DUO" }, @@ -7983,6 +8007,9 @@ "inviteMember": { "message": "Покана на член" }, + "addSponsorship": { + "message": "Add sponsorship" + }, "needsConfirmation": { "message": "Изисква потвърждение" }, @@ -10571,20 +10598,11 @@ "removeUnlockWithPinPolicyDesc": { "message": "Забраняване на членовете да отключват акаунтите си с ПИН." }, - "limitedEventLogs": { - "message": "Плановете от тип „$PRODUCT_TYPE$“ нямат достъп до истинските журнали на събитията", - "placeholders": { - "product_type": { - "content": "$1", - "example": "Teams" - } - } + "upgradeForFullEventsMessage": { + "message": "Event logs are not stored for your organization. Upgrade to a Teams or Enterprise plan to get full access to organization event logs." }, - "upgradeForFullEvents": { - "message": "Получете пълен достъп до журналите на събитията за организациите като надградите до Екипния план или този за Големи организации." - }, - "upgradeEventLogTitle": { - "message": "Надградете за достъп до истинските журнали на събитията" + "upgradeEventLogTitleMessage": { + "message": "Upgrade to see event logs from your organization." }, "upgradeEventLogMessage": { "message": "Тези събития са само за пример и не отразяват истинските събития във Вашата организация." @@ -10600,5 +10618,8 @@ }, "newBusinessUnit": { "message": "Нова бизнес единица" + }, + "restart": { + "message": "Повторно пускане" } } diff --git a/apps/web/src/locales/bn/messages.json b/apps/web/src/locales/bn/messages.json index dc879696b3c..5aca52aeabc 100644 --- a/apps/web/src/locales/bn/messages.json +++ b/apps/web/src/locales/bn/messages.json @@ -2,6 +2,9 @@ "allApplications": { "message": "All applications" }, + "appLogoLabel": { + "message": "Bitwarden logo" + }, "criticalApplications": { "message": "Critical applications" }, @@ -419,18 +422,9 @@ "folder": { "message": "ফোল্ডার" }, - "newCustomField": { - "message": "নতুন পছন্দসই ক্ষেত্র" - }, "value": { "message": "মান" }, - "dragToSort": { - "message": "বাছাই করতে টানুন" - }, - "dragToReorder": { - "message": "Drag to reorder" - }, "cfTypeText": { "message": "পাঠ্য" }, @@ -4072,8 +4066,8 @@ "updateBrowser": { "message": "Update browser" }, - "generatingRiskInsights": { - "message": "Generating your risk insights..." + "generatingYourRiskInsights": { + "message": "Generating your Risk Insights..." }, "updateBrowserDesc": { "message": "You are using an unsupported web browser. The web vault may not function properly." @@ -6305,6 +6299,21 @@ "sponsoredFamilies": { "message": "Free Bitwarden Families" }, + "sponsoredBitwardenFamilies": { + "message": "Sponsored families" + }, + "noSponsoredFamilies": { + "message": "No sponsored families" + }, + "noSponsoredFamiliesDescription": { + "message": "Sponsored non-member families plans will display here" + }, + "sponsorFreeBitwardenFamilies": { + "message": "Members of your organization are eligible for Free Bitwarden Families. You can sponsor Free Bitwarden Families for employees who are not a member of your Bitwarden organization. Sponsoring a non-member requires an available seat within your organization." + }, + "sponsoredFamiliesRemoveActiveSponsorship": { + "message": "When you remove an active sponsorship, a seat within your organization will be available after the renewal date of the sponsored organization." + }, "sponsoredFamiliesEligible": { "message": "You and your family are eligible for Free Bitwarden Families. Redeem with your personal email to keep your data secure even when you are not at work." }, @@ -6320,6 +6329,18 @@ "sponsoredFamiliesSharedCollections": { "message": "Shared collections for Family secrets" }, + "memberFamilies": { + "message": "Member families" + }, + "noMemberFamilies": { + "message": "No member families" + }, + "noMemberFamiliesDescription": { + "message": "Members who have redeemed family plans will display here" + }, + "membersWithSponsoredFamilies": { + "message": "Members of your organization are eligible for Free Bitwarden Families. Here you can see members who have sponsored a Families organization." + }, "badToken": { "message": "The link is no longer valid. Please have the sponsor resend the offer." }, @@ -7249,6 +7270,9 @@ "followTheStepsBelowToFinishLoggingIn": { "message": "Follow the steps below to finish logging in." }, + "followTheStepsBelowToFinishLoggingInWithSecurityKey": { + "message": "Follow the steps below to finish logging in with your security key." + }, "launchDuo": { "message": "Launch Duo" }, @@ -7983,6 +8007,9 @@ "inviteMember": { "message": "Invite member" }, + "addSponsorship": { + "message": "Add sponsorship" + }, "needsConfirmation": { "message": "Needs confirmation" }, @@ -10571,20 +10598,11 @@ "removeUnlockWithPinPolicyDesc": { "message": "Do not allow members to unlock their account with a PIN." }, - "limitedEventLogs": { - "message": "$PRODUCT_TYPE$ plans do not have access to real event logs", - "placeholders": { - "product_type": { - "content": "$1", - "example": "Teams" - } - } + "upgradeForFullEventsMessage": { + "message": "Event logs are not stored for your organization. Upgrade to a Teams or Enterprise plan to get full access to organization event logs." }, - "upgradeForFullEvents": { - "message": "Get full access to organization event logs by upgrading to a Teams or Enterprise plan." - }, - "upgradeEventLogTitle": { - "message": "Upgrade for real event log data" + "upgradeEventLogTitleMessage": { + "message": "Upgrade to see event logs from your organization." }, "upgradeEventLogMessage": { "message": "These events are examples only and do not reflect real events within your Bitwarden organization." @@ -10600,5 +10618,8 @@ }, "newBusinessUnit": { "message": "New business unit" + }, + "restart": { + "message": "Restart" } } diff --git a/apps/web/src/locales/bs/messages.json b/apps/web/src/locales/bs/messages.json index aea556cde36..9502a7a9e1f 100644 --- a/apps/web/src/locales/bs/messages.json +++ b/apps/web/src/locales/bs/messages.json @@ -2,6 +2,9 @@ "allApplications": { "message": "All applications" }, + "appLogoLabel": { + "message": "Bitwarden logo" + }, "criticalApplications": { "message": "Critical applications" }, @@ -419,18 +422,9 @@ "folder": { "message": "Fascikla" }, - "newCustomField": { - "message": "Novo Prilagođeno Polje" - }, "value": { "message": "Vrijednost" }, - "dragToSort": { - "message": "Povuci za sortiranje" - }, - "dragToReorder": { - "message": "Drag to reorder" - }, "cfTypeText": { "message": "Tekst" }, @@ -4072,8 +4066,8 @@ "updateBrowser": { "message": "Update browser" }, - "generatingRiskInsights": { - "message": "Generating your risk insights..." + "generatingYourRiskInsights": { + "message": "Generating your Risk Insights..." }, "updateBrowserDesc": { "message": "You are using an unsupported web browser. The web vault may not function properly." @@ -6305,6 +6299,21 @@ "sponsoredFamilies": { "message": "Free Bitwarden Families" }, + "sponsoredBitwardenFamilies": { + "message": "Sponsored families" + }, + "noSponsoredFamilies": { + "message": "No sponsored families" + }, + "noSponsoredFamiliesDescription": { + "message": "Sponsored non-member families plans will display here" + }, + "sponsorFreeBitwardenFamilies": { + "message": "Members of your organization are eligible for Free Bitwarden Families. You can sponsor Free Bitwarden Families for employees who are not a member of your Bitwarden organization. Sponsoring a non-member requires an available seat within your organization." + }, + "sponsoredFamiliesRemoveActiveSponsorship": { + "message": "When you remove an active sponsorship, a seat within your organization will be available after the renewal date of the sponsored organization." + }, "sponsoredFamiliesEligible": { "message": "You and your family are eligible for Free Bitwarden Families. Redeem with your personal email to keep your data secure even when you are not at work." }, @@ -6320,6 +6329,18 @@ "sponsoredFamiliesSharedCollections": { "message": "Shared collections for Family secrets" }, + "memberFamilies": { + "message": "Member families" + }, + "noMemberFamilies": { + "message": "No member families" + }, + "noMemberFamiliesDescription": { + "message": "Members who have redeemed family plans will display here" + }, + "membersWithSponsoredFamilies": { + "message": "Members of your organization are eligible for Free Bitwarden Families. Here you can see members who have sponsored a Families organization." + }, "badToken": { "message": "The link is no longer valid. Please have the sponsor resend the offer." }, @@ -7249,6 +7270,9 @@ "followTheStepsBelowToFinishLoggingIn": { "message": "Follow the steps below to finish logging in." }, + "followTheStepsBelowToFinishLoggingInWithSecurityKey": { + "message": "Follow the steps below to finish logging in with your security key." + }, "launchDuo": { "message": "Launch Duo" }, @@ -7983,6 +8007,9 @@ "inviteMember": { "message": "Invite member" }, + "addSponsorship": { + "message": "Add sponsorship" + }, "needsConfirmation": { "message": "Needs confirmation" }, @@ -10571,20 +10598,11 @@ "removeUnlockWithPinPolicyDesc": { "message": "Do not allow members to unlock their account with a PIN." }, - "limitedEventLogs": { - "message": "$PRODUCT_TYPE$ plans do not have access to real event logs", - "placeholders": { - "product_type": { - "content": "$1", - "example": "Teams" - } - } + "upgradeForFullEventsMessage": { + "message": "Event logs are not stored for your organization. Upgrade to a Teams or Enterprise plan to get full access to organization event logs." }, - "upgradeForFullEvents": { - "message": "Get full access to organization event logs by upgrading to a Teams or Enterprise plan." - }, - "upgradeEventLogTitle": { - "message": "Upgrade for real event log data" + "upgradeEventLogTitleMessage": { + "message": "Upgrade to see event logs from your organization." }, "upgradeEventLogMessage": { "message": "These events are examples only and do not reflect real events within your Bitwarden organization." @@ -10600,5 +10618,8 @@ }, "newBusinessUnit": { "message": "New business unit" + }, + "restart": { + "message": "Restart" } } diff --git a/apps/web/src/locales/ca/messages.json b/apps/web/src/locales/ca/messages.json index 93fa6e5793b..cc920f0ffdf 100644 --- a/apps/web/src/locales/ca/messages.json +++ b/apps/web/src/locales/ca/messages.json @@ -2,6 +2,9 @@ "allApplications": { "message": "Totes les aplicacions" }, + "appLogoLabel": { + "message": "Bitwarden logo" + }, "criticalApplications": { "message": "Aplicacions crítiques" }, @@ -419,18 +422,9 @@ "folder": { "message": "Carpeta" }, - "newCustomField": { - "message": "Nou camp personalitzat" - }, "value": { "message": "Valor" }, - "dragToSort": { - "message": "Arrossega per ordenar" - }, - "dragToReorder": { - "message": "Drag to reorder" - }, "cfTypeText": { "message": "Text" }, @@ -4072,8 +4066,8 @@ "updateBrowser": { "message": "Actualitza el navegador" }, - "generatingRiskInsights": { - "message": "Generating your risk insights..." + "generatingYourRiskInsights": { + "message": "Generating your Risk Insights..." }, "updateBrowserDesc": { "message": "Esteu utilitzant un navegador web no compatible. La caixa forta web pot no funcionar correctament." @@ -6305,6 +6299,21 @@ "sponsoredFamilies": { "message": "Famílies Bitwarden gratuït" }, + "sponsoredBitwardenFamilies": { + "message": "Sponsored families" + }, + "noSponsoredFamilies": { + "message": "No sponsored families" + }, + "noSponsoredFamiliesDescription": { + "message": "Sponsored non-member families plans will display here" + }, + "sponsorFreeBitwardenFamilies": { + "message": "Members of your organization are eligible for Free Bitwarden Families. You can sponsor Free Bitwarden Families for employees who are not a member of your Bitwarden organization. Sponsoring a non-member requires an available seat within your organization." + }, + "sponsoredFamiliesRemoveActiveSponsorship": { + "message": "When you remove an active sponsorship, a seat within your organization will be available after the renewal date of the sponsored organization." + }, "sponsoredFamiliesEligible": { "message": "Tu i la teua família sou elegibles per a famílies Bitwarden gratuït. Bescanvia amb el correu electrònic personal per mantenir les dades segures fins i tot quan no esteu a la feina." }, @@ -6320,6 +6329,18 @@ "sponsoredFamiliesSharedCollections": { "message": "Col·leccions compartides de Secrets familiars" }, + "memberFamilies": { + "message": "Member families" + }, + "noMemberFamilies": { + "message": "No member families" + }, + "noMemberFamiliesDescription": { + "message": "Members who have redeemed family plans will display here" + }, + "membersWithSponsoredFamilies": { + "message": "Members of your organization are eligible for Free Bitwarden Families. Here you can see members who have sponsored a Families organization." + }, "badToken": { "message": "L'enllaç ja no és vàlid. Fes que el patrocinador torne a enviar l'oferta." }, @@ -7249,6 +7270,9 @@ "followTheStepsBelowToFinishLoggingIn": { "message": "Follow the steps below to finish logging in." }, + "followTheStepsBelowToFinishLoggingInWithSecurityKey": { + "message": "Follow the steps below to finish logging in with your security key." + }, "launchDuo": { "message": "Inicia DUO" }, @@ -7983,6 +8007,9 @@ "inviteMember": { "message": "Convida membre" }, + "addSponsorship": { + "message": "Add sponsorship" + }, "needsConfirmation": { "message": "Necessita confirmació" }, @@ -10571,20 +10598,11 @@ "removeUnlockWithPinPolicyDesc": { "message": "Do not allow members to unlock their account with a PIN." }, - "limitedEventLogs": { - "message": "$PRODUCT_TYPE$ plans do not have access to real event logs", - "placeholders": { - "product_type": { - "content": "$1", - "example": "Teams" - } - } + "upgradeForFullEventsMessage": { + "message": "Event logs are not stored for your organization. Upgrade to a Teams or Enterprise plan to get full access to organization event logs." }, - "upgradeForFullEvents": { - "message": "Get full access to organization event logs by upgrading to a Teams or Enterprise plan." - }, - "upgradeEventLogTitle": { - "message": "Upgrade for real event log data" + "upgradeEventLogTitleMessage": { + "message": "Upgrade to see event logs from your organization." }, "upgradeEventLogMessage": { "message": "These events are examples only and do not reflect real events within your Bitwarden organization." @@ -10600,5 +10618,8 @@ }, "newBusinessUnit": { "message": "Unitat de negoci nova" + }, + "restart": { + "message": "Restart" } } diff --git a/apps/web/src/locales/cs/messages.json b/apps/web/src/locales/cs/messages.json index 35b0904bda2..980d69cf15c 100644 --- a/apps/web/src/locales/cs/messages.json +++ b/apps/web/src/locales/cs/messages.json @@ -2,6 +2,9 @@ "allApplications": { "message": "Všechny aplikace" }, + "appLogoLabel": { + "message": "Logo Bitwarden" + }, "criticalApplications": { "message": "Kritické aplikace" }, @@ -419,18 +422,9 @@ "folder": { "message": "Složka" }, - "newCustomField": { - "message": "Nové vlastní pole" - }, "value": { "message": "Hodnota" }, - "dragToSort": { - "message": "Přetáhnutím seřadíte" - }, - "dragToReorder": { - "message": "Přesuňte tažením" - }, "cfTypeText": { "message": "Text" }, @@ -4072,7 +4066,7 @@ "updateBrowser": { "message": "Aktualizovat prohlížeč" }, - "generatingRiskInsights": { + "generatingYourRiskInsights": { "message": "Generování poznatků o rizicích..." }, "updateBrowserDesc": { @@ -6305,6 +6299,21 @@ "sponsoredFamilies": { "message": "Bitwarden pro rodinu zdarma" }, + "sponsoredBitwardenFamilies": { + "message": "Sponzorované rodiny" + }, + "noSponsoredFamilies": { + "message": "Žádné sponzorované rodiny" + }, + "noSponsoredFamiliesDescription": { + "message": "Zde se zobrazí sponzorované plány rodin, které nejsou členy" + }, + "sponsorFreeBitwardenFamilies": { + "message": "Členové vaší organizace mají nárok na bezplatné rodiny Bitwarden. Můžete sponzorovat zdarma rodiny Bitwarden pro zaměstnance, kteří nejsou členy Vaší organizace Bitwarden. Sponzorování nečlena vyžaduje dostupné místo ve Vaší organizaci." + }, + "sponsoredFamiliesRemoveActiveSponsorship": { + "message": "Pokud odeberete aktivní sponzorství, bude po datu obnovení sponzorované organizace k dispozici volný uživatel ve Vaší organizaci." + }, "sponsoredFamiliesEligible": { "message": "Vy a Vaše rodina máte nárok na Bitwarden pro rodinu zdarma. Nárok můžete uplatnit Vaším osobním e-mailem, abyste zajistili bezpečnost Vašich dat, i když nejste v práci." }, @@ -6320,6 +6329,18 @@ "sponsoredFamiliesSharedCollections": { "message": "Sdílené kolekce pro tajné klíče rodiny" }, + "memberFamilies": { + "message": "Rodiny členů" + }, + "noMemberFamilies": { + "message": "Žádné rodiny členů" + }, + "noMemberFamiliesDescription": { + "message": "Zde se zobrazí členové, kteří využili rodinné plány" + }, + "membersWithSponsoredFamilies": { + "message": "Členové Vaší organizace mají nárok na bezplatné rodiny Bitwarden. Zde můžete vidět členy, kteří sponzorovali organizaci rodin." + }, "badToken": { "message": "Odkaz již není platný. Požádejte sponzora o opakované odeslání nabídky." }, @@ -7249,6 +7270,9 @@ "followTheStepsBelowToFinishLoggingIn": { "message": "Postupujte podle kroků níže pro dokončení přihlášení." }, + "followTheStepsBelowToFinishLoggingInWithSecurityKey": { + "message": "Postupujte podle následujících kroků pro dokončení přihlášení Vaším bezpečnostním klíčem." + }, "launchDuo": { "message": "Spustit Duo" }, @@ -7983,6 +8007,9 @@ "inviteMember": { "message": "Pozvat uživatele" }, + "addSponsorship": { + "message": "Přidat sponzorství" + }, "needsConfirmation": { "message": "Vyžaduje potvrzení" }, @@ -10571,20 +10598,11 @@ "removeUnlockWithPinPolicyDesc": { "message": "Nepovolí členům odemknout svůj účet pomocí PIN." }, - "limitedEventLogs": { - "message": "Plány $PRODUCT_TYPE$ nemají přístup k protokolům reálných událostí", - "placeholders": { - "product_type": { - "content": "$1", - "example": "Teams" - } - } + "upgradeForFullEventsMessage": { + "message": "Event logs are not stored for your organization. Upgrade to a Teams or Enterprise plan to get full access to organization event logs." }, - "upgradeForFullEvents": { - "message": "Získejte plný přístup k protokolům událostí organizace aktualizací do týmů nebo plánu Enterprise." - }, - "upgradeEventLogTitle": { - "message": "Aktualizovat pro reálná data protokolu událostí" + "upgradeEventLogTitleMessage": { + "message": "Upgrade to see event logs from your organization." }, "upgradeEventLogMessage": { "message": "Tyto události jsou jen příklady a neodrážejí skutečné události v rámci Vaší organizace Bitwarden." @@ -10600,5 +10618,8 @@ }, "newBusinessUnit": { "message": "Nová obchodní jednotka" + }, + "restart": { + "message": "Restartovat" } } diff --git a/apps/web/src/locales/cy/messages.json b/apps/web/src/locales/cy/messages.json index 69217c7f17b..05bcdf1e236 100644 --- a/apps/web/src/locales/cy/messages.json +++ b/apps/web/src/locales/cy/messages.json @@ -2,6 +2,9 @@ "allApplications": { "message": "All applications" }, + "appLogoLabel": { + "message": "Bitwarden logo" + }, "criticalApplications": { "message": "Critical applications" }, @@ -419,18 +422,9 @@ "folder": { "message": "Ffolder" }, - "newCustomField": { - "message": "New custom field" - }, "value": { "message": "Value" }, - "dragToSort": { - "message": "Drag to sort" - }, - "dragToReorder": { - "message": "Drag to reorder" - }, "cfTypeText": { "message": "Text" }, @@ -4072,8 +4066,8 @@ "updateBrowser": { "message": "Update browser" }, - "generatingRiskInsights": { - "message": "Generating your risk insights..." + "generatingYourRiskInsights": { + "message": "Generating your Risk Insights..." }, "updateBrowserDesc": { "message": "You are using an unsupported web browser. The web vault may not function properly." @@ -6305,6 +6299,21 @@ "sponsoredFamilies": { "message": "Free Bitwarden Families" }, + "sponsoredBitwardenFamilies": { + "message": "Sponsored families" + }, + "noSponsoredFamilies": { + "message": "No sponsored families" + }, + "noSponsoredFamiliesDescription": { + "message": "Sponsored non-member families plans will display here" + }, + "sponsorFreeBitwardenFamilies": { + "message": "Members of your organization are eligible for Free Bitwarden Families. You can sponsor Free Bitwarden Families for employees who are not a member of your Bitwarden organization. Sponsoring a non-member requires an available seat within your organization." + }, + "sponsoredFamiliesRemoveActiveSponsorship": { + "message": "When you remove an active sponsorship, a seat within your organization will be available after the renewal date of the sponsored organization." + }, "sponsoredFamiliesEligible": { "message": "You and your family are eligible for Free Bitwarden Families. Redeem with your personal email to keep your data secure even when you are not at work." }, @@ -6320,6 +6329,18 @@ "sponsoredFamiliesSharedCollections": { "message": "Shared collections for Family secrets" }, + "memberFamilies": { + "message": "Member families" + }, + "noMemberFamilies": { + "message": "No member families" + }, + "noMemberFamiliesDescription": { + "message": "Members who have redeemed family plans will display here" + }, + "membersWithSponsoredFamilies": { + "message": "Members of your organization are eligible for Free Bitwarden Families. Here you can see members who have sponsored a Families organization." + }, "badToken": { "message": "The link is no longer valid. Please have the sponsor resend the offer." }, @@ -7249,6 +7270,9 @@ "followTheStepsBelowToFinishLoggingIn": { "message": "Follow the steps below to finish logging in." }, + "followTheStepsBelowToFinishLoggingInWithSecurityKey": { + "message": "Follow the steps below to finish logging in with your security key." + }, "launchDuo": { "message": "Launch Duo" }, @@ -7983,6 +8007,9 @@ "inviteMember": { "message": "Invite member" }, + "addSponsorship": { + "message": "Add sponsorship" + }, "needsConfirmation": { "message": "Needs confirmation" }, @@ -10571,20 +10598,11 @@ "removeUnlockWithPinPolicyDesc": { "message": "Do not allow members to unlock their account with a PIN." }, - "limitedEventLogs": { - "message": "$PRODUCT_TYPE$ plans do not have access to real event logs", - "placeholders": { - "product_type": { - "content": "$1", - "example": "Teams" - } - } + "upgradeForFullEventsMessage": { + "message": "Event logs are not stored for your organization. Upgrade to a Teams or Enterprise plan to get full access to organization event logs." }, - "upgradeForFullEvents": { - "message": "Get full access to organization event logs by upgrading to a Teams or Enterprise plan." - }, - "upgradeEventLogTitle": { - "message": "Upgrade for real event log data" + "upgradeEventLogTitleMessage": { + "message": "Upgrade to see event logs from your organization." }, "upgradeEventLogMessage": { "message": "These events are examples only and do not reflect real events within your Bitwarden organization." @@ -10600,5 +10618,8 @@ }, "newBusinessUnit": { "message": "New business unit" + }, + "restart": { + "message": "Restart" } } diff --git a/apps/web/src/locales/da/messages.json b/apps/web/src/locales/da/messages.json index 9999432c50d..b48ce90b75f 100644 --- a/apps/web/src/locales/da/messages.json +++ b/apps/web/src/locales/da/messages.json @@ -2,6 +2,9 @@ "allApplications": { "message": "Alle applikationer" }, + "appLogoLabel": { + "message": "Bitwarden logo" + }, "criticalApplications": { "message": "Kritiske apps" }, @@ -419,18 +422,9 @@ "folder": { "message": "Mappe" }, - "newCustomField": { - "message": "Nyt tilpasset felt" - }, "value": { "message": "Værdi" }, - "dragToSort": { - "message": "Træk for at sortere" - }, - "dragToReorder": { - "message": "Drag to reorder" - }, "cfTypeText": { "message": "Tekst" }, @@ -4072,8 +4066,8 @@ "updateBrowser": { "message": "Opdatér browser" }, - "generatingRiskInsights": { - "message": "Genererer risikoindsigter..." + "generatingYourRiskInsights": { + "message": "Generating your Risk Insights..." }, "updateBrowserDesc": { "message": "Du bruger en ikke-understøttet webbrowser. Web-boksen fungerer muligvis ikke korrekt." @@ -6305,6 +6299,21 @@ "sponsoredFamilies": { "message": "Gratis Bitwarden Familier" }, + "sponsoredBitwardenFamilies": { + "message": "Sponsored families" + }, + "noSponsoredFamilies": { + "message": "No sponsored families" + }, + "noSponsoredFamiliesDescription": { + "message": "Sponsored non-member families plans will display here" + }, + "sponsorFreeBitwardenFamilies": { + "message": "Members of your organization are eligible for Free Bitwarden Families. You can sponsor Free Bitwarden Families for employees who are not a member of your Bitwarden organization. Sponsoring a non-member requires an available seat within your organization." + }, + "sponsoredFamiliesRemoveActiveSponsorship": { + "message": "When you remove an active sponsorship, a seat within your organization will be available after the renewal date of the sponsored organization." + }, "sponsoredFamiliesEligible": { "message": "Du og din familie er berettiget til gratis Bitwarden Familier. Indløs med din personlige e-mail for at holde dine data sikre, selv når du ikke er på arbejde." }, @@ -6320,6 +6329,18 @@ "sponsoredFamiliesSharedCollections": { "message": "Delte samlinger til Familiehemmeligheder" }, + "memberFamilies": { + "message": "Member families" + }, + "noMemberFamilies": { + "message": "No member families" + }, + "noMemberFamiliesDescription": { + "message": "Members who have redeemed family plans will display here" + }, + "membersWithSponsoredFamilies": { + "message": "Members of your organization are eligible for Free Bitwarden Families. Here you can see members who have sponsored a Families organization." + }, "badToken": { "message": "Linket er ikke længere gyldigt. Bed sponsoren sende et nyt tilbud." }, @@ -7249,6 +7270,9 @@ "followTheStepsBelowToFinishLoggingIn": { "message": "Follow the steps below to finish logging in." }, + "followTheStepsBelowToFinishLoggingInWithSecurityKey": { + "message": "Follow the steps below to finish logging in with your security key." + }, "launchDuo": { "message": "Start Duo" }, @@ -7983,6 +8007,9 @@ "inviteMember": { "message": "Invitér medlem" }, + "addSponsorship": { + "message": "Add sponsorship" + }, "needsConfirmation": { "message": "Behøver bekræftelse" }, @@ -10571,20 +10598,11 @@ "removeUnlockWithPinPolicyDesc": { "message": "Do not allow members to unlock their account with a PIN." }, - "limitedEventLogs": { - "message": "$PRODUCT_TYPE$ plans do not have access to real event logs", - "placeholders": { - "product_type": { - "content": "$1", - "example": "Teams" - } - } + "upgradeForFullEventsMessage": { + "message": "Event logs are not stored for your organization. Upgrade to a Teams or Enterprise plan to get full access to organization event logs." }, - "upgradeForFullEvents": { - "message": "Get full access to organization event logs by upgrading to a Teams or Enterprise plan." - }, - "upgradeEventLogTitle": { - "message": "Upgrade for real event log data" + "upgradeEventLogTitleMessage": { + "message": "Upgrade to see event logs from your organization." }, "upgradeEventLogMessage": { "message": "These events are examples only and do not reflect real events within your Bitwarden organization." @@ -10600,5 +10618,8 @@ }, "newBusinessUnit": { "message": "New business unit" + }, + "restart": { + "message": "Restart" } } diff --git a/apps/web/src/locales/de/messages.json b/apps/web/src/locales/de/messages.json index a517629a5b4..173dbb28311 100644 --- a/apps/web/src/locales/de/messages.json +++ b/apps/web/src/locales/de/messages.json @@ -2,6 +2,9 @@ "allApplications": { "message": "Alle Anwendungen" }, + "appLogoLabel": { + "message": "Bitwarden-Logo" + }, "criticalApplications": { "message": "Kritische Anwendungen" }, @@ -419,18 +422,9 @@ "folder": { "message": "Ordner" }, - "newCustomField": { - "message": "Neues benutzerdefiniertes Feld" - }, "value": { "message": "Wert" }, - "dragToSort": { - "message": "Zum Sortieren ziehen" - }, - "dragToReorder": { - "message": "Ziehen zum Umsortieren" - }, "cfTypeText": { "message": "Text" }, @@ -3324,10 +3318,10 @@ "message": "Die externe ID ist eine unverschlüsselte Referenz, die vom Bitwarden Directory Connector und der API verwendet wird." }, "ssoExternalId": { - "message": "SSO External ID" + "message": "Externe SSO-ID" }, "ssoExternalIdDesc": { - "message": "SSO External ID is an unencrypted reference between Bitwarden and your configured SSO provider." + "message": "Die externe SSO-ID ist eine unverschlüsselte Referenz zwischen Bitwarden und deinem konfigurierten SSO-Anbieter." }, "nestCollectionUnder": { "message": "Sammlung verschachteln unter" @@ -4072,7 +4066,7 @@ "updateBrowser": { "message": "Browser aktualisieren" }, - "generatingRiskInsights": { + "generatingYourRiskInsights": { "message": "Dein Risiko-Überblick wird generiert..." }, "updateBrowserDesc": { @@ -6305,6 +6299,21 @@ "sponsoredFamilies": { "message": "Kostenloses Bitwarden Families" }, + "sponsoredBitwardenFamilies": { + "message": "Gesponserte Familien" + }, + "noSponsoredFamilies": { + "message": "Keine gesponserten Familien" + }, + "noSponsoredFamiliesDescription": { + "message": "Gesponserte Families-Tarife von Nicht-Mitgliedern werden hier angezeigt" + }, + "sponsorFreeBitwardenFamilies": { + "message": "Mitglieder deiner Organisation sind für den kostenlosen Bitwarden Families-Tarif berechtigt. Du kannst kostenlose Bitwarden Families-Tarife für Mitarbeiter sponsern, die kein Mitglied deiner Bitwarden Organisation sind. Das Sponsoring eines Nicht-Mitglieds erfordert einen verfügbaren Benutzerplatz in deiner Organisation." + }, + "sponsoredFamiliesRemoveActiveSponsorship": { + "message": "Wenn du ein aktives Sponsoring entfernst, steht dir nach dem Verlängerungsdatum der gesponserten Organisation ein Benutzerplatz in deiner Organisation zur Verfügung." + }, "sponsoredFamiliesEligible": { "message": "Du und deine Familie haben Anspruch auf Free Bitwarden Families. Mit deiner persönlichen E-Mail-Adresse einlösen, um deine Daten zu schützen, auch wenn du nicht am Arbeitsplatz bist." }, @@ -6320,6 +6329,18 @@ "sponsoredFamiliesSharedCollections": { "message": "Gemeinsame Sammlungen für Familiengeheimnisse" }, + "memberFamilies": { + "message": "Mitgliederfamilien" + }, + "noMemberFamilies": { + "message": "Keine Mitgliederfamilien" + }, + "noMemberFamiliesDescription": { + "message": "Mitglieder, die Families-Tarife eingelöst haben, werden hier angezeigt" + }, + "membersWithSponsoredFamilies": { + "message": "Mitglieder deiner Organisation sind für den kostenlosen Bitwarden Families-Tarif berechtigt. Hier kannst du Mitglieder sehen, die eine Familien-Organisation gesponsert haben." + }, "badToken": { "message": "Der Link ist nicht mehr gültig. Bitte lasse dir vom Sponsor das Angebot erneut senden." }, @@ -7249,6 +7270,9 @@ "followTheStepsBelowToFinishLoggingIn": { "message": "Folge den Schritten unten, um die Anmeldung abzuschließen." }, + "followTheStepsBelowToFinishLoggingInWithSecurityKey": { + "message": "Folge den Schritten unten, um die Anmeldung mit deinem Sicherheitsschlüssel abzuschließen." + }, "launchDuo": { "message": "DUO starten" }, @@ -7983,6 +8007,9 @@ "inviteMember": { "message": "Mitglied einladen" }, + "addSponsorship": { + "message": "Sponsoring hinzufügen" + }, "needsConfirmation": { "message": "Bestätigung erforderlich" }, @@ -10343,7 +10370,7 @@ "message": "Der Name der Organisation darf 50 Zeichen nicht überschreiten." }, "rotationCompletedTitle": { - "message": "Schlüsselrotation erfolgreich" + "message": "Schlüsselerneuerung erfolgreich" }, "rotationCompletedDesc": { "message": "Dein Master-Passwort und Verschlüsselungsschlüssel wurden aktualisiert. Deine anderen Geräte wurden abgemeldet." @@ -10557,7 +10584,7 @@ } }, "userkeyRotationDisclaimerDescription": { - "message": "Die Rotation deiner Verschlüsselungsschlüssel erfordert, den Schlüsseln aller Organisationen, die dein Konto wiederherstellen können, sowie aller Kontakte, für die du den Notfallzugriff aktiviert hast, zu vertrauen. Um fortzufahren, stelle sicher, dass du Folgendes bestätigen kannst:" + "message": "Die Erneuerung deiner Verschlüsselungsschlüssel erfordert, den Schlüsseln aller Organisationen zu vertrauen, die dein Konto wiederherstellen können, sowie aller Kontakte, für die du den Notfallzugriff aktiviert hast. Um fortzufahren, stelle sicher, dass du Folgendes bestätigen kannst:" }, "userkeyRotationDisclaimerTitle": { "message": "Nicht vertrauenswürdige Verschlüsselungsschlüssel" @@ -10571,20 +10598,11 @@ "removeUnlockWithPinPolicyDesc": { "message": "Mitgliedern nicht erlauben, ihr Konto mit einer PIN zu entsperren." }, - "limitedEventLogs": { - "message": "$PRODUCT_TYPE$-Abos haben keinen Zugriff auf echte Ereignisprotokolle", - "placeholders": { - "product_type": { - "content": "$1", - "example": "Teams" - } - } + "upgradeForFullEventsMessage": { + "message": "Event logs are not stored for your organization. Upgrade to a Teams or Enterprise plan to get full access to organization event logs." }, - "upgradeForFullEvents": { - "message": "Erhalte vollen Zugriff auf Ereignisprotokolle von Organisationen durch ein Upgrade auf ein Teams- oder Enterprise-Abo." - }, - "upgradeEventLogTitle": { - "message": "Upgrade für echte Ereignisprotokolldaten" + "upgradeEventLogTitleMessage": { + "message": "Upgrade to see event logs from your organization." }, "upgradeEventLogMessage": { "message": "Diese Ereignisse sind nur Beispiele und spiegeln keine realen Ereignisse in deiner Bitwarden-Organisation wider." @@ -10593,12 +10611,15 @@ "message": "Kostenlose Organisationen können bis zu 2 Sammlungen haben. Upgrade auf ein kostenpflichtiges Abo, um mehr Sammlungen hinzuzufügen." }, "businessUnit": { - "message": "Business Unit" + "message": "Geschäftsbereich" }, "businessUnits": { - "message": "Business Units" + "message": "Geschäftsbereiche" }, "newBusinessUnit": { - "message": "New business unit" + "message": "Neuer Geschäftsbereich" + }, + "restart": { + "message": "Restart" } } diff --git a/apps/web/src/locales/el/messages.json b/apps/web/src/locales/el/messages.json index b44892b1276..f14fe091a52 100644 --- a/apps/web/src/locales/el/messages.json +++ b/apps/web/src/locales/el/messages.json @@ -2,6 +2,9 @@ "allApplications": { "message": "Όλες οι εφαρμογές" }, + "appLogoLabel": { + "message": "Bitwarden logo" + }, "criticalApplications": { "message": "Κρίσιμες εφαρμογές" }, @@ -419,18 +422,9 @@ "folder": { "message": "Φάκελος" }, - "newCustomField": { - "message": "Νέο προσαρμοσμένο πεδίο" - }, "value": { "message": "Τιμή" }, - "dragToSort": { - "message": "Σύρετε για ταξινόμηση" - }, - "dragToReorder": { - "message": "Drag to reorder" - }, "cfTypeText": { "message": "Κείμενο" }, @@ -4072,8 +4066,8 @@ "updateBrowser": { "message": "Ενημερώστε τον Browser" }, - "generatingRiskInsights": { - "message": "Generating your risk insights..." + "generatingYourRiskInsights": { + "message": "Generating your Risk Insights..." }, "updateBrowserDesc": { "message": "Χρησιμοποιείτε ένα μη υποστηριζόμενο browser. Το web vault ενδέχεται να μην λειτουργεί σωστά." @@ -6305,6 +6299,21 @@ "sponsoredFamilies": { "message": "Δωρεάν Bitwarden Families" }, + "sponsoredBitwardenFamilies": { + "message": "Sponsored families" + }, + "noSponsoredFamilies": { + "message": "No sponsored families" + }, + "noSponsoredFamiliesDescription": { + "message": "Sponsored non-member families plans will display here" + }, + "sponsorFreeBitwardenFamilies": { + "message": "Members of your organization are eligible for Free Bitwarden Families. You can sponsor Free Bitwarden Families for employees who are not a member of your Bitwarden organization. Sponsoring a non-member requires an available seat within your organization." + }, + "sponsoredFamiliesRemoveActiveSponsorship": { + "message": "When you remove an active sponsorship, a seat within your organization will be available after the renewal date of the sponsored organization." + }, "sponsoredFamiliesEligible": { "message": "Εσείς και η οικογένεια σας έχετε δικαίωμα για Δωρεάν Bitwarden Families. Εξαργυρώστε με το προσωπικό σας email για να διατηρείτε τα δεδομένα σας ασφαλή ακόμα και όταν δεν εργάζεστε." }, @@ -6320,6 +6329,18 @@ "sponsoredFamiliesSharedCollections": { "message": "Κοινές συλλογές για οικογενειακά μυστικά" }, + "memberFamilies": { + "message": "Member families" + }, + "noMemberFamilies": { + "message": "No member families" + }, + "noMemberFamiliesDescription": { + "message": "Members who have redeemed family plans will display here" + }, + "membersWithSponsoredFamilies": { + "message": "Members of your organization are eligible for Free Bitwarden Families. Here you can see members who have sponsored a Families organization." + }, "badToken": { "message": "Ο σύνδεσμος δεν είναι πλέον έγκυρος. Παρακαλώ στείλτε ξανά την προσφορά." }, @@ -7249,6 +7270,9 @@ "followTheStepsBelowToFinishLoggingIn": { "message": "Follow the steps below to finish logging in." }, + "followTheStepsBelowToFinishLoggingInWithSecurityKey": { + "message": "Follow the steps below to finish logging in with your security key." + }, "launchDuo": { "message": "Εκκίνηση Duo" }, @@ -7983,6 +8007,9 @@ "inviteMember": { "message": "Πρόσκληση μέλους" }, + "addSponsorship": { + "message": "Add sponsorship" + }, "needsConfirmation": { "message": "Χρειάζεται επιβεβαίωση" }, @@ -10571,20 +10598,11 @@ "removeUnlockWithPinPolicyDesc": { "message": "Do not allow members to unlock their account with a PIN." }, - "limitedEventLogs": { - "message": "$PRODUCT_TYPE$ plans do not have access to real event logs", - "placeholders": { - "product_type": { - "content": "$1", - "example": "Teams" - } - } + "upgradeForFullEventsMessage": { + "message": "Event logs are not stored for your organization. Upgrade to a Teams or Enterprise plan to get full access to organization event logs." }, - "upgradeForFullEvents": { - "message": "Get full access to organization event logs by upgrading to a Teams or Enterprise plan." - }, - "upgradeEventLogTitle": { - "message": "Upgrade for real event log data" + "upgradeEventLogTitleMessage": { + "message": "Upgrade to see event logs from your organization." }, "upgradeEventLogMessage": { "message": "These events are examples only and do not reflect real events within your Bitwarden organization." @@ -10600,5 +10618,8 @@ }, "newBusinessUnit": { "message": "New business unit" + }, + "restart": { + "message": "Restart" } } diff --git a/apps/web/src/locales/en/messages.json b/apps/web/src/locales/en/messages.json index 6a400839a77..4cb81cafebd 100644 --- a/apps/web/src/locales/en/messages.json +++ b/apps/web/src/locales/en/messages.json @@ -422,18 +422,9 @@ "folder": { "message": "Folder" }, - "newCustomField": { - "message": "New custom field" - }, "value": { "message": "Value" }, - "dragToSort": { - "message": "Drag to sort" - }, - "dragToReorder": { - "message": "Drag to reorder" - }, "cfTypeText": { "message": "Text" }, @@ -6312,13 +6303,13 @@ "sponsoredBitwardenFamilies": { "message": "Sponsored families" }, - "noSponsoredFamilies": { + "noSponsoredFamiliesMessage": { "message": "No sponsored families" }, - "noSponsoredFamiliesDescription": { + "nosponsoredFamiliesDetails": { "message": "Sponsored non-member families plans will display here" }, - "sponsorFreeBitwardenFamilies": { + "sponsorshipFreeBitwardenFamilies": { "message": "Members of your organization are eligible for Free Bitwarden Families. You can sponsor Free Bitwarden Families for employees who are not a member of your Bitwarden organization. Sponsoring a non-member requires an available seat within your organization." }, "sponsoredFamiliesRemoveActiveSponsorship": { @@ -6330,14 +6321,14 @@ "sponsoredFamiliesEligibleCard": { "message": "Redeem your Free Bitwarden for Families plan today to keep your data secure even when you are not at work." }, - "sponsoredFamiliesInclude": { - "message": "The Bitwarden for Families plan include" + "sponsoredFamiliesIncludeMessage": { + "message": "The Bitwarden for Families plan includes" }, "sponsoredFamiliesPremiumAccess": { "message": "Premium access for up to 6 users" }, - "sponsoredFamiliesSharedCollections": { - "message": "Shared collections for Family secrets" + "sponsoredFamiliesSharedCollectionsForFamilyMembers": { + "message": "Shared collections for family members" }, "memberFamilies": { "message": "Member families" @@ -6351,6 +6342,15 @@ "membersWithSponsoredFamilies": { "message": "Members of your organization are eligible for Free Bitwarden Families. Here you can see members who have sponsored a Families organization." }, + "organizationHasMemberMessage": { + "message": "A sponsorship cannot be sent to $EMAIL$ because they are a member of your organization.", + "placeholders": { + "email": { + "content": "$1", + "example": "mail@example.com" + } + } + }, "badToken": { "message": "The link is no longer valid. Please have the sponsor resend the offer." }, @@ -6402,7 +6402,7 @@ "redeemedAccount": { "message": "Account redeemed" }, - "revokeAccount": { + "revokeAccountMessage": { "message": "Revoke account $NAME$", "placeholders": { "name": { @@ -7280,6 +7280,9 @@ "followTheStepsBelowToFinishLoggingIn": { "message": "Follow the steps below to finish logging in." }, + "followTheStepsBelowToFinishLoggingInWithSecurityKey": { + "message": "Follow the steps below to finish logging in with your security key." + }, "launchDuo": { "message": "Launch Duo" }, @@ -10605,20 +10608,11 @@ "removeUnlockWithPinPolicyDesc": { "message": "Do not allow members to unlock their account with a PIN." }, - "limitedEventLogs": { - "message": "$PRODUCT_TYPE$ plans do not have access to real event logs", - "placeholders": { - "product_type": { - "content": "$1", - "example": "Teams" - } - } + "upgradeForFullEventsMessage": { + "message": "Event logs are not stored for your organization. Upgrade to a Teams or Enterprise plan to get full access to organization event logs." }, - "upgradeForFullEvents": { - "message": "Get full access to organization event logs by upgrading to a Teams or Enterprise plan." - }, - "upgradeEventLogTitle" : { - "message" : "Upgrade for real event log data" + "upgradeEventLogTitleMessage" : { + "message" : "Upgrade to see event logs from your organization." }, "upgradeEventLogMessage":{ "message" : "These events are examples only and do not reflect real events within your Bitwarden organization." @@ -10646,5 +10640,41 @@ }, "newBusinessUnit": { "message": "New business unit" + }, + "newLoginNudgeTitle": { + "message": "Save time with autofill" + }, + "newLoginNudgeBody": { + "message": "Include a Website so this login appears as an autofill suggestion." + }, + "newCardNudgeTitle": { + "message": "Seamless online checkout" + }, + "newCardNudgeBody": { + "message": "With cards, easily autofill payment forms securely and accurately." + }, + "newIdentityNudgeTitle": { + "message": "Simplify creating accounts" + }, + "newIdentityNudgeBody": { + "message": "With identities, quickly autofill long registration or contact forms." + }, + "newNoteNudgeTitle": { + "message": "Keep your sensitive data safe" + }, + "newNoteNudgeBody": { + "message": "With notes, securely store sensitive data like banking or insurance details." + }, + "newSshNudgeTitle": { + "message": "Developer-friendly SSH access" + }, + "newSshNudgeBody": { + "message": "Store your keys and connect with the SSH agent for fast, encrypted authentication." + }, + "restart": { + "message": "Restart" + }, + "verifyProviderBankAccountWithStatementDescriptorWarning": { + "message": "Payment with a bank account is only available to customers in the United States. You will be required to verify your bank account. We will make a micro-deposit within the next 1-2 business days. Enter the statement descriptor code from this deposit on the provider's subscription page to verify the bank account. Failure to verify the bank account will result in a missed payment and your subscription being suspended." } } diff --git a/apps/web/src/locales/en_GB/messages.json b/apps/web/src/locales/en_GB/messages.json index daef0bc6945..4b46d5578da 100644 --- a/apps/web/src/locales/en_GB/messages.json +++ b/apps/web/src/locales/en_GB/messages.json @@ -2,6 +2,9 @@ "allApplications": { "message": "All applications" }, + "appLogoLabel": { + "message": "Bitwarden logo" + }, "criticalApplications": { "message": "Critical applications" }, @@ -419,18 +422,9 @@ "folder": { "message": "Folder" }, - "newCustomField": { - "message": "New custom field" - }, "value": { "message": "Value" }, - "dragToSort": { - "message": "Drag to sort" - }, - "dragToReorder": { - "message": "Drag to reorder" - }, "cfTypeText": { "message": "Text" }, @@ -4072,8 +4066,8 @@ "updateBrowser": { "message": "Update browser" }, - "generatingRiskInsights": { - "message": "Generating your risk insights..." + "generatingYourRiskInsights": { + "message": "Generating your Risk Insights..." }, "updateBrowserDesc": { "message": "You are using an unsupported web browser. The web vault may not function properly." @@ -6305,6 +6299,21 @@ "sponsoredFamilies": { "message": "Free Bitwarden Families" }, + "sponsoredBitwardenFamilies": { + "message": "Sponsored families" + }, + "noSponsoredFamilies": { + "message": "No sponsored families" + }, + "noSponsoredFamiliesDescription": { + "message": "Sponsored non-member families plans will display here" + }, + "sponsorFreeBitwardenFamilies": { + "message": "Members of your organisation are eligible for Free Bitwarden Families. You can sponsor Free Bitwarden Families for employees who are not a member of your Bitwarden organisation. Sponsoring a non-member requires an available seat within your organisation." + }, + "sponsoredFamiliesRemoveActiveSponsorship": { + "message": "When you remove an active sponsorship, a seat within your organisation will be available after the renewal date of the sponsored organisation." + }, "sponsoredFamiliesEligible": { "message": "You and your family are eligible for Free Bitwarden Families. Redeem with your personal email to keep your data secure even when you are not at work." }, @@ -6320,6 +6329,18 @@ "sponsoredFamiliesSharedCollections": { "message": "Shared collections for Family secrets" }, + "memberFamilies": { + "message": "Member families" + }, + "noMemberFamilies": { + "message": "No member families" + }, + "noMemberFamiliesDescription": { + "message": "Members who have redeemed family plans will display here" + }, + "membersWithSponsoredFamilies": { + "message": "Members of your organisation are eligible for Free Bitwarden Families. Here you can see members who have sponsored a Families organisation." + }, "badToken": { "message": "The link is no longer valid. Please have the sponsor resend the offer." }, @@ -7249,6 +7270,9 @@ "followTheStepsBelowToFinishLoggingIn": { "message": "Follow the steps below to finish logging in." }, + "followTheStepsBelowToFinishLoggingInWithSecurityKey": { + "message": "Follow the steps below to finish logging in with your security key." + }, "launchDuo": { "message": "Launch Duo" }, @@ -7983,6 +8007,9 @@ "inviteMember": { "message": "Invite member" }, + "addSponsorship": { + "message": "Add sponsorship" + }, "needsConfirmation": { "message": "Needs confirmation" }, @@ -10571,20 +10598,11 @@ "removeUnlockWithPinPolicyDesc": { "message": "Do not allow members to unlock their account with a PIN." }, - "limitedEventLogs": { - "message": "$PRODUCT_TYPE$ plans do not have access to real event logs", - "placeholders": { - "product_type": { - "content": "$1", - "example": "Teams" - } - } + "upgradeForFullEventsMessage": { + "message": "Event logs are not stored for your organization. Upgrade to a Teams or Enterprise plan to get full access to organization event logs." }, - "upgradeForFullEvents": { - "message": "Get full access to organisation event logs by upgrading to a Teams or Enterprise plan." - }, - "upgradeEventLogTitle": { - "message": "Upgrade for real event log data" + "upgradeEventLogTitleMessage": { + "message": "Upgrade to see event logs from your organization." }, "upgradeEventLogMessage": { "message": "These events are examples only and do not reflect real events within your Bitwarden organisation." @@ -10600,5 +10618,8 @@ }, "newBusinessUnit": { "message": "New business unit" + }, + "restart": { + "message": "Restart" } } diff --git a/apps/web/src/locales/en_IN/messages.json b/apps/web/src/locales/en_IN/messages.json index 0804aa60e08..bb3aefc1735 100644 --- a/apps/web/src/locales/en_IN/messages.json +++ b/apps/web/src/locales/en_IN/messages.json @@ -2,6 +2,9 @@ "allApplications": { "message": "All applications" }, + "appLogoLabel": { + "message": "Bitwarden logo" + }, "criticalApplications": { "message": "Critical applications" }, @@ -419,18 +422,9 @@ "folder": { "message": "Folder" }, - "newCustomField": { - "message": "New custom field" - }, "value": { "message": "Value" }, - "dragToSort": { - "message": "Drag to sort" - }, - "dragToReorder": { - "message": "Drag to reorder" - }, "cfTypeText": { "message": "Text" }, @@ -4072,8 +4066,8 @@ "updateBrowser": { "message": "Update browser" }, - "generatingRiskInsights": { - "message": "Generating your risk insights..." + "generatingYourRiskInsights": { + "message": "Generating your Risk Insights..." }, "updateBrowserDesc": { "message": "You are using an unsupported web browser. The web vault may not function properly." @@ -6305,6 +6299,21 @@ "sponsoredFamilies": { "message": "Free Bitwarden Families" }, + "sponsoredBitwardenFamilies": { + "message": "Sponsored families" + }, + "noSponsoredFamilies": { + "message": "No sponsored families" + }, + "noSponsoredFamiliesDescription": { + "message": "Sponsored non-member families plans will display here" + }, + "sponsorFreeBitwardenFamilies": { + "message": "Members of your organisation are eligible for Free Bitwarden Families. You can sponsor Free Bitwarden Families for employees who are not a member of your Bitwarden organisation. Sponsoring a non-member requires an available seat within your organisation." + }, + "sponsoredFamiliesRemoveActiveSponsorship": { + "message": "When you remove an active sponsorship, a seat within your organisation will be available after the renewal date of the sponsored organisation." + }, "sponsoredFamiliesEligible": { "message": "You and your family are eligible for Free Bitwarden Families. Redeem with your personal email to keep your data secure even when you are not at work." }, @@ -6320,6 +6329,18 @@ "sponsoredFamiliesSharedCollections": { "message": "Shared collections for Family secrets" }, + "memberFamilies": { + "message": "Member families" + }, + "noMemberFamilies": { + "message": "No member families" + }, + "noMemberFamiliesDescription": { + "message": "Members who have redeemed family plans will display here" + }, + "membersWithSponsoredFamilies": { + "message": "Members of your organisation are eligible for Free Bitwarden Families. Here you can see members who have sponsored a Families organisation." + }, "badToken": { "message": "The link is no longer valid. Please have the sponsor resend the offer." }, @@ -7249,6 +7270,9 @@ "followTheStepsBelowToFinishLoggingIn": { "message": "Follow the steps below to finish logging in." }, + "followTheStepsBelowToFinishLoggingInWithSecurityKey": { + "message": "Follow the steps below to finish logging in with your security key." + }, "launchDuo": { "message": "Launch Duo" }, @@ -7983,6 +8007,9 @@ "inviteMember": { "message": "Invite member" }, + "addSponsorship": { + "message": "Add sponsorship" + }, "needsConfirmation": { "message": "Needs confirmation" }, @@ -10571,20 +10598,11 @@ "removeUnlockWithPinPolicyDesc": { "message": "Do not allow members to unlock their account with a PIN." }, - "limitedEventLogs": { - "message": "$PRODUCT_TYPE$ plans do not have access to real event logs", - "placeholders": { - "product_type": { - "content": "$1", - "example": "Teams" - } - } + "upgradeForFullEventsMessage": { + "message": "Event logs are not stored for your organization. Upgrade to a Teams or Enterprise plan to get full access to organization event logs." }, - "upgradeForFullEvents": { - "message": "Get full access to organisation event logs by upgrading to a Teams or Enterprise plan." - }, - "upgradeEventLogTitle": { - "message": "Upgrade for real event log data" + "upgradeEventLogTitleMessage": { + "message": "Upgrade to see event logs from your organization." }, "upgradeEventLogMessage": { "message": "These events are examples only and do not reflect real events within your Bitwarden organisation." @@ -10600,5 +10618,8 @@ }, "newBusinessUnit": { "message": "New business unit" + }, + "restart": { + "message": "Restart" } } diff --git a/apps/web/src/locales/eo/messages.json b/apps/web/src/locales/eo/messages.json index 33afe669868..6163a21edbe 100644 --- a/apps/web/src/locales/eo/messages.json +++ b/apps/web/src/locales/eo/messages.json @@ -2,6 +2,9 @@ "allApplications": { "message": "Ĉiuj aplikaĵoj" }, + "appLogoLabel": { + "message": "Bitwarden logo" + }, "criticalApplications": { "message": "Critical applications" }, @@ -419,18 +422,9 @@ "folder": { "message": "Dosierujo" }, - "newCustomField": { - "message": "Nova propra kampo" - }, "value": { "message": "Valoro" }, - "dragToSort": { - "message": "Trenu por ordigi" - }, - "dragToReorder": { - "message": "Drag to reorder" - }, "cfTypeText": { "message": "Teksto" }, @@ -4072,8 +4066,8 @@ "updateBrowser": { "message": "Ĝisdatigi retumilon" }, - "generatingRiskInsights": { - "message": "Generating your risk insights..." + "generatingYourRiskInsights": { + "message": "Generating your Risk Insights..." }, "updateBrowserDesc": { "message": "Vi uzas nesubtenatan tTT-legilon. La ttt-volbo eble ne funkcias ĝuste." @@ -6305,6 +6299,21 @@ "sponsoredFamilies": { "message": "Free Bitwarden Families" }, + "sponsoredBitwardenFamilies": { + "message": "Sponsored families" + }, + "noSponsoredFamilies": { + "message": "No sponsored families" + }, + "noSponsoredFamiliesDescription": { + "message": "Sponsored non-member families plans will display here" + }, + "sponsorFreeBitwardenFamilies": { + "message": "Members of your organization are eligible for Free Bitwarden Families. You can sponsor Free Bitwarden Families for employees who are not a member of your Bitwarden organization. Sponsoring a non-member requires an available seat within your organization." + }, + "sponsoredFamiliesRemoveActiveSponsorship": { + "message": "When you remove an active sponsorship, a seat within your organization will be available after the renewal date of the sponsored organization." + }, "sponsoredFamiliesEligible": { "message": "You and your family are eligible for Free Bitwarden Families. Redeem with your personal email to keep your data secure even when you are not at work." }, @@ -6320,6 +6329,18 @@ "sponsoredFamiliesSharedCollections": { "message": "Shared collections for Family secrets" }, + "memberFamilies": { + "message": "Member families" + }, + "noMemberFamilies": { + "message": "No member families" + }, + "noMemberFamiliesDescription": { + "message": "Members who have redeemed family plans will display here" + }, + "membersWithSponsoredFamilies": { + "message": "Members of your organization are eligible for Free Bitwarden Families. Here you can see members who have sponsored a Families organization." + }, "badToken": { "message": "The link is no longer valid. Please have the sponsor resend the offer." }, @@ -7249,6 +7270,9 @@ "followTheStepsBelowToFinishLoggingIn": { "message": "Follow the steps below to finish logging in." }, + "followTheStepsBelowToFinishLoggingInWithSecurityKey": { + "message": "Follow the steps below to finish logging in with your security key." + }, "launchDuo": { "message": "Launch Duo" }, @@ -7983,6 +8007,9 @@ "inviteMember": { "message": "Invite member" }, + "addSponsorship": { + "message": "Add sponsorship" + }, "needsConfirmation": { "message": "Needs confirmation" }, @@ -10571,20 +10598,11 @@ "removeUnlockWithPinPolicyDesc": { "message": "Do not allow members to unlock their account with a PIN." }, - "limitedEventLogs": { - "message": "$PRODUCT_TYPE$ plans do not have access to real event logs", - "placeholders": { - "product_type": { - "content": "$1", - "example": "Teams" - } - } + "upgradeForFullEventsMessage": { + "message": "Event logs are not stored for your organization. Upgrade to a Teams or Enterprise plan to get full access to organization event logs." }, - "upgradeForFullEvents": { - "message": "Get full access to organization event logs by upgrading to a Teams or Enterprise plan." - }, - "upgradeEventLogTitle": { - "message": "Upgrade for real event log data" + "upgradeEventLogTitleMessage": { + "message": "Upgrade to see event logs from your organization." }, "upgradeEventLogMessage": { "message": "These events are examples only and do not reflect real events within your Bitwarden organization." @@ -10600,5 +10618,8 @@ }, "newBusinessUnit": { "message": "New business unit" + }, + "restart": { + "message": "Restart" } } diff --git a/apps/web/src/locales/es/messages.json b/apps/web/src/locales/es/messages.json index d3f9c608748..d73d5f344d1 100644 --- a/apps/web/src/locales/es/messages.json +++ b/apps/web/src/locales/es/messages.json @@ -2,6 +2,9 @@ "allApplications": { "message": "Todas las aplicaciones" }, + "appLogoLabel": { + "message": "Bitwarden logo" + }, "criticalApplications": { "message": "Aplicaciones críticas" }, @@ -419,18 +422,9 @@ "folder": { "message": "Carpeta" }, - "newCustomField": { - "message": "Nuevo campo personalizado" - }, "value": { "message": "Valor" }, - "dragToSort": { - "message": "Arrastra para ordenar" - }, - "dragToReorder": { - "message": "Drag to reorder" - }, "cfTypeText": { "message": "Texto" }, @@ -4072,8 +4066,8 @@ "updateBrowser": { "message": "Actualizar navegador" }, - "generatingRiskInsights": { - "message": "Generating your risk insights..." + "generatingYourRiskInsights": { + "message": "Generating your Risk Insights..." }, "updateBrowserDesc": { "message": "Está utilizando un navegador web no compatible. Es posible que la caja fuerte web no funcione correctamente." @@ -6305,6 +6299,21 @@ "sponsoredFamilies": { "message": "Familias Bitwarden gratis" }, + "sponsoredBitwardenFamilies": { + "message": "Sponsored families" + }, + "noSponsoredFamilies": { + "message": "No sponsored families" + }, + "noSponsoredFamiliesDescription": { + "message": "Sponsored non-member families plans will display here" + }, + "sponsorFreeBitwardenFamilies": { + "message": "Members of your organization are eligible for Free Bitwarden Families. You can sponsor Free Bitwarden Families for employees who are not a member of your Bitwarden organization. Sponsoring a non-member requires an available seat within your organization." + }, + "sponsoredFamiliesRemoveActiveSponsorship": { + "message": "When you remove an active sponsorship, a seat within your organization will be available after the renewal date of the sponsored organization." + }, "sponsoredFamiliesEligible": { "message": "Usted y su familia son elegibles para Familias Bitwarden Gratis. Canjeé con su correo electrónico personal para mantener tus datos seguros incluso cuando no estés en el trabajo." }, @@ -6320,6 +6329,18 @@ "sponsoredFamiliesSharedCollections": { "message": "Colecciones compartidas para Secretos Familiares" }, + "memberFamilies": { + "message": "Member families" + }, + "noMemberFamilies": { + "message": "No member families" + }, + "noMemberFamiliesDescription": { + "message": "Members who have redeemed family plans will display here" + }, + "membersWithSponsoredFamilies": { + "message": "Members of your organization are eligible for Free Bitwarden Families. Here you can see members who have sponsored a Families organization." + }, "badToken": { "message": "El enlace ya no es válido. Por favor, solicite al patrocinados que vuelva a enviar la oferta." }, @@ -7249,6 +7270,9 @@ "followTheStepsBelowToFinishLoggingIn": { "message": "Follow the steps below to finish logging in." }, + "followTheStepsBelowToFinishLoggingInWithSecurityKey": { + "message": "Follow the steps below to finish logging in with your security key." + }, "launchDuo": { "message": "Iniciar Duo" }, @@ -7983,6 +8007,9 @@ "inviteMember": { "message": "Invitar miembro" }, + "addSponsorship": { + "message": "Add sponsorship" + }, "needsConfirmation": { "message": "Necesita confirmación" }, @@ -10571,20 +10598,11 @@ "removeUnlockWithPinPolicyDesc": { "message": "Do not allow members to unlock their account with a PIN." }, - "limitedEventLogs": { - "message": "$PRODUCT_TYPE$ plans do not have access to real event logs", - "placeholders": { - "product_type": { - "content": "$1", - "example": "Teams" - } - } + "upgradeForFullEventsMessage": { + "message": "Event logs are not stored for your organization. Upgrade to a Teams or Enterprise plan to get full access to organization event logs." }, - "upgradeForFullEvents": { - "message": "Get full access to organization event logs by upgrading to a Teams or Enterprise plan." - }, - "upgradeEventLogTitle": { - "message": "Upgrade for real event log data" + "upgradeEventLogTitleMessage": { + "message": "Upgrade to see event logs from your organization." }, "upgradeEventLogMessage": { "message": "These events are examples only and do not reflect real events within your Bitwarden organization." @@ -10600,5 +10618,8 @@ }, "newBusinessUnit": { "message": "New business unit" + }, + "restart": { + "message": "Restart" } } diff --git a/apps/web/src/locales/et/messages.json b/apps/web/src/locales/et/messages.json index b5ed1f22f61..175b4fff363 100644 --- a/apps/web/src/locales/et/messages.json +++ b/apps/web/src/locales/et/messages.json @@ -1,6 +1,9 @@ { "allApplications": { - "message": "All applications" + "message": "Kõik rakendused" + }, + "appLogoLabel": { + "message": "Bitwardeni logo" }, "criticalApplications": { "message": "Critical applications" @@ -21,7 +24,7 @@ "message": "Review at-risk passwords (weak, exposed, or reused) across applications. Select your most critical applications to prioritize security actions for your users to address at-risk passwords." }, "dataLastUpdated": { - "message": "Data last updated: $DATE$", + "message": "Viimati uuendatud: $DATE$", "placeholders": { "date": { "content": "$1", @@ -30,19 +33,19 @@ } }, "notifiedMembers": { - "message": "Notified members" + "message": "Teavitatud liikmed" }, "revokeMembers": { - "message": "Revoke members" + "message": "Lõpeta liikmesus" }, "restoreMembers": { - "message": "Restore members" + "message": "Taasta liikmesus" }, "cannotRestoreAccessError": { - "message": "Cannot restore organization access" + "message": "Ei õnnestunud taastada ligipääsu organisatsioonile" }, "allApplicationsWithCount": { - "message": "All applications ($COUNT$)", + "message": "Kõik rakendused ($COUNT$)", "placeholders": { "count": { "content": "$1", @@ -51,7 +54,7 @@ } }, "createNewLoginItem": { - "message": "Create new login item" + "message": "Loo uus kirje" }, "criticalApplicationsWithCount": { "message": "Critical applications ($COUNT$)", @@ -63,7 +66,7 @@ } }, "notifiedMembersWithCount": { - "message": "Notified members ($COUNT$)", + "message": "Teavitatud liikmed ($COUNT$)", "placeholders": { "count": { "content": "$1", @@ -312,7 +315,7 @@ "message": "Kaardi turvakood (CVV)" }, "securityCodeSlashCVV": { - "message": "Security code / CVV" + "message": "Kaardi turvakood / CVV" }, "identityName": { "message": "Identiteedi nimi" @@ -419,18 +422,9 @@ "folder": { "message": "Kaust" }, - "newCustomField": { - "message": "Uus kohandatud väli" - }, "value": { "message": "Väärtus" }, - "dragToSort": { - "message": "Lohista sorteerimiseks" - }, - "dragToReorder": { - "message": "Drag to reorder" - }, "cfTypeText": { "message": "Tekst" }, @@ -441,7 +435,7 @@ "message": "Boolean" }, "cfTypeCheckbox": { - "message": "Checkbox" + "message": "Märkeruut" }, "cfTypeLinked": { "message": "Seotud", @@ -487,7 +481,7 @@ } }, "newFolder": { - "message": "New folder" + "message": "Uus kaust" }, "folderName": { "message": "Folder name" @@ -838,25 +832,25 @@ "message": "Copy address" }, "copyPhone": { - "message": "Copy phone" + "message": "Kopeeri telefoninumber" }, "copyEmail": { - "message": "Copy email" + "message": "Kopeeri e-posti aadress" }, "copyCompany": { - "message": "Copy company" + "message": "Kopeeri firma nimi" }, "copySSN": { - "message": "Copy Social Security number" + "message": "Kopeeri isikukood" }, "copyPassportNumber": { - "message": "Copy passport number" + "message": "Kopeeri passi number" }, "copyLicenseNumber": { - "message": "Copy license number" + "message": "Kopeeri litsentsi number" }, "copyName": { - "message": "Copy name" + "message": "Kopeeri nimi" }, "me": { "message": "Mina" @@ -1037,7 +1031,7 @@ "message": "Ei" }, "location": { - "message": "Location" + "message": "Asukoht" }, "loginOrCreateNewAccount": { "message": "Logi sisse või loo uus konto." @@ -1064,13 +1058,13 @@ "message": "Kasuta teist logimismeetodit" }, "logInWithPasskey": { - "message": "Log in with passkey" + "message": "Logi sisse pääsuvõtmega" }, "useSingleSignOn": { "message": "Use single sign-on" }, "welcomeBack": { - "message": "Welcome back" + "message": "Tere tulemast tagasi" }, "invalidPasskeyPleaseTryAgain": { "message": "Vigane pääsuvõti. Palun proovi uuesti." @@ -1154,7 +1148,7 @@ "message": "Konto loomine" }, "newToBitwarden": { - "message": "New to Bitwarden?" + "message": "Esimest korda siin?" }, "setAStrongPassword": { "message": "Määra tugev parool" @@ -1172,16 +1166,16 @@ "message": "Logi sisse" }, "logInToBitwarden": { - "message": "Log in to Bitwarden" + "message": "Logi sisse Bitwardenisse" }, "enterTheCodeSentToYourEmail": { - "message": "Enter the code sent to your email" + "message": "Sisesta oma emailile saadetud kood" }, "enterTheCodeFromYourAuthenticatorApp": { - "message": "Enter the code from your authenticator app" + "message": "Sisesta kood oma autentimisrakendusest" }, "pressYourYubiKeyToAuthenticate": { - "message": "Press your YubiKey to authenticate" + "message": "Autentimiseks vajuta nuppu oma YubiKey'l" }, "authenticationTimeout": { "message": "Authentication timeout" @@ -1190,25 +1184,25 @@ "message": "The authentication session timed out. Please restart the login process." }, "verifyYourIdentity": { - "message": "Verify your Identity" + "message": "Kinnitage oma Identiteet" }, "weDontRecognizeThisDevice": { - "message": "We don't recognize this device. Enter the code sent to your email to verify your identity." + "message": "Me ei tunne seda seadet. Enda identiteedi kinnitamiseks palun sisesta oma emailile saadetud kood." }, "continueLoggingIn": { - "message": "Continue logging in" + "message": "Jätka sisse logimist" }, "whatIsADevice": { - "message": "What is a device?" + "message": "Mis asi on seade?" }, "aDeviceIs": { - "message": "A device is a unique installation of the Bitwarden app where you have logged in. Reinstalling, clearing app data, or clearing your cookies could result in a device appearing multiple times." + "message": "Seade on ainulaadne Bitwardeni rakendus, kus sa oled sisse logitud. Rakenduse uuesti installimine, andmete kustutamine või küpsiste kustutamine võib tekitada olukorra, kus üks seade esineb mitu korda." }, "logInInitiated": { "message": "Sisselogimine käivitatud" }, "logInRequestSent": { - "message": "Request sent" + "message": "Päring saadetud" }, "submit": { "message": "Kinnita" @@ -1263,16 +1257,16 @@ "message": "Seaded" }, "accountEmail": { - "message": "Account email" + "message": "Konto e-maili aadress" }, "requestHint": { - "message": "Request hint" + "message": "Küsi vihjet" }, "requestPasswordHint": { - "message": "Request password hint" + "message": "Küsi parooli vihjet" }, "enterYourAccountEmailAddressAndYourPasswordHintWillBeSentToYou": { - "message": "Enter your account email address and your password hint will be sent to you" + "message": "Sisesta oma e-maili aadress ja sulle saadetakse sinna parooli vihje" }, "getMasterPasswordHint": { "message": "Tuleta ülemparooli vihjega meelde" @@ -1330,7 +1324,7 @@ "message": "Sinu hoidla on lukus." }, "yourAccountIsLocked": { - "message": "Your account is locked" + "message": "Sinu konto on lukustatud" }, "uuid": { "message": "UUID" @@ -1397,7 +1391,7 @@ "message": "Unlock Bitwarden on your device or on the " }, "areYouTryingToAccessYourAccount": { - "message": "Are you trying to access your account?" + "message": "Kas sa püüad praegu oma kontole sisse logida?" }, "accessAttemptBy": { "message": "Access attempt by $EMAIL$", @@ -1409,13 +1403,13 @@ } }, "confirmAccess": { - "message": "Confirm access" + "message": "Kinnita juurdepääs" }, "denyAccess": { - "message": "Deny access" + "message": "Keeldu juurdepääsu andmast" }, "notificationSentDeviceAnchor": { - "message": "web app" + "message": "veebirakendus" }, "notificationSentDevicePart2": { "message": "Make sure the Fingerprint phrase matches the one below before approving." @@ -1424,7 +1418,7 @@ "message": "Unlock Bitwarden on your device. Make sure the Fingerprint phrase matches the one below before approving." }, "aNotificationWasSentToYourDevice": { - "message": "A notification was sent to your device" + "message": "Sinu seadmele saadeti teavitus" }, "versionNumber": { "message": "Versioon $VERSION_NUMBER$", @@ -1445,14 +1439,14 @@ } }, "dontAskAgainOnThisDeviceFor30Days": { - "message": "Don't ask again on this device for 30 days" + "message": "Ära küsi 30 päeva jooksul uuesti" }, "selectAnotherMethod": { - "message": "Select another method", + "message": "Vali teine meetod", "description": "Select another two-step login method" }, "useYourRecoveryCode": { - "message": "Use your recovery code" + "message": "Kasuta taastamise koodi" }, "insertU2f": { "message": "Sisesta oma turvaline võti arvuti USB porti. Kui sellel on nupp, siis vajuta seda." @@ -1470,7 +1464,7 @@ "message": "Kaheastmelise sisselogimise valikud" }, "selectTwoStepLoginMethod": { - "message": "Select two-step login method" + "message": "Vali kaheastmelise sisselogimise meetod" }, "recoveryCodeDesc": { "message": "Sul ei ole ligipääsu ühelegi kaheastmelise kinnitamise teenusele? Kasuta taastamise koodi, et kaheastmeline kinnitamine oma kontol välja lülitada." @@ -1515,7 +1509,7 @@ "message": "(pärineb FIDO'lt)" }, "openInNewTab": { - "message": "Open in new tab" + "message": "Ava uuel vahelehel" }, "emailTitle": { "message": "E-post" @@ -1682,7 +1676,7 @@ "description": "deprecated. Use avoidAmbiguous instead." }, "avoidAmbiguous": { - "message": "Avoid ambiguous characters", + "message": "Väldi raskesti eristatavaid tähti ja sümboleid", "description": "Label for the avoid ambiguous characters checkbox." }, "length": { @@ -3913,7 +3907,7 @@ "message": "Device Type" }, "ipAddress": { - "message": "IP Address" + "message": "IP aadress" }, "confirmLogIn": { "message": "Confirm login" @@ -4072,8 +4066,8 @@ "updateBrowser": { "message": "Uuenda brauserit" }, - "generatingRiskInsights": { - "message": "Generating your risk insights..." + "generatingYourRiskInsights": { + "message": "Generating your Risk Insights..." }, "updateBrowserDesc": { "message": "Kasutad brauserit, mida ei toetata. Veebihoidla ei pruugi hästi töötada." @@ -6305,6 +6299,21 @@ "sponsoredFamilies": { "message": "Tasuta Bitwarden Families pakett" }, + "sponsoredBitwardenFamilies": { + "message": "Sponsored families" + }, + "noSponsoredFamilies": { + "message": "No sponsored families" + }, + "noSponsoredFamiliesDescription": { + "message": "Sponsored non-member families plans will display here" + }, + "sponsorFreeBitwardenFamilies": { + "message": "Members of your organization are eligible for Free Bitwarden Families. You can sponsor Free Bitwarden Families for employees who are not a member of your Bitwarden organization. Sponsoring a non-member requires an available seat within your organization." + }, + "sponsoredFamiliesRemoveActiveSponsorship": { + "message": "When you remove an active sponsorship, a seat within your organization will be available after the renewal date of the sponsored organization." + }, "sponsoredFamiliesEligible": { "message": "Sina ja su pere olete valitud tasuta Bitwarden Families paketi saamiseks. Lunasta see läbi oma emaili, et hoida oma andmed ohutus kohas isegi kui sa ei ole tööl." }, @@ -6320,6 +6329,18 @@ "sponsoredFamiliesSharedCollections": { "message": "Jagatud kogumikud pere saladuste talletamiseks" }, + "memberFamilies": { + "message": "Member families" + }, + "noMemberFamilies": { + "message": "No member families" + }, + "noMemberFamiliesDescription": { + "message": "Members who have redeemed family plans will display here" + }, + "membersWithSponsoredFamilies": { + "message": "Members of your organization are eligible for Free Bitwarden Families. Here you can see members who have sponsored a Families organization." + }, "badToken": { "message": "See link ei ole enam aktiivne. Uue lingi saamiseks kontakteeruge oma sponsoriga, et ta saadaks pakkumise uuesti." }, @@ -7249,6 +7270,9 @@ "followTheStepsBelowToFinishLoggingIn": { "message": "Follow the steps below to finish logging in." }, + "followTheStepsBelowToFinishLoggingInWithSecurityKey": { + "message": "Follow the steps below to finish logging in with your security key." + }, "launchDuo": { "message": "Launch Duo" }, @@ -7983,6 +8007,9 @@ "inviteMember": { "message": "Invite member" }, + "addSponsorship": { + "message": "Add sponsorship" + }, "needsConfirmation": { "message": "Needs confirmation" }, @@ -10571,20 +10598,11 @@ "removeUnlockWithPinPolicyDesc": { "message": "Do not allow members to unlock their account with a PIN." }, - "limitedEventLogs": { - "message": "$PRODUCT_TYPE$ plans do not have access to real event logs", - "placeholders": { - "product_type": { - "content": "$1", - "example": "Teams" - } - } + "upgradeForFullEventsMessage": { + "message": "Event logs are not stored for your organization. Upgrade to a Teams or Enterprise plan to get full access to organization event logs." }, - "upgradeForFullEvents": { - "message": "Get full access to organization event logs by upgrading to a Teams or Enterprise plan." - }, - "upgradeEventLogTitle": { - "message": "Upgrade for real event log data" + "upgradeEventLogTitleMessage": { + "message": "Upgrade to see event logs from your organization." }, "upgradeEventLogMessage": { "message": "These events are examples only and do not reflect real events within your Bitwarden organization." @@ -10600,5 +10618,8 @@ }, "newBusinessUnit": { "message": "New business unit" + }, + "restart": { + "message": "Restart" } } diff --git a/apps/web/src/locales/eu/messages.json b/apps/web/src/locales/eu/messages.json index 06a13bb6765..19cba9439a2 100644 --- a/apps/web/src/locales/eu/messages.json +++ b/apps/web/src/locales/eu/messages.json @@ -2,6 +2,9 @@ "allApplications": { "message": "All applications" }, + "appLogoLabel": { + "message": "Bitwarden logo" + }, "criticalApplications": { "message": "Critical applications" }, @@ -419,18 +422,9 @@ "folder": { "message": "Karpeta" }, - "newCustomField": { - "message": "Eremu pertsonalizatu berria" - }, "value": { "message": "Balioa" }, - "dragToSort": { - "message": "Arrastatu txukuntzeko" - }, - "dragToReorder": { - "message": "Drag to reorder" - }, "cfTypeText": { "message": "Testua" }, @@ -4072,8 +4066,8 @@ "updateBrowser": { "message": "Nabigatzailea eguneratu" }, - "generatingRiskInsights": { - "message": "Generating your risk insights..." + "generatingYourRiskInsights": { + "message": "Generating your Risk Insights..." }, "updateBrowserDesc": { "message": "Euskarririk gabeko web nabigatzailea erabiltzen ari zara. Baliteke webguneko kutxa gotorrak behar bezala ez funtzionatzea." @@ -6305,6 +6299,21 @@ "sponsoredFamilies": { "message": "Doako Bitwarden Familia" }, + "sponsoredBitwardenFamilies": { + "message": "Sponsored families" + }, + "noSponsoredFamilies": { + "message": "No sponsored families" + }, + "noSponsoredFamiliesDescription": { + "message": "Sponsored non-member families plans will display here" + }, + "sponsorFreeBitwardenFamilies": { + "message": "Members of your organization are eligible for Free Bitwarden Families. You can sponsor Free Bitwarden Families for employees who are not a member of your Bitwarden organization. Sponsoring a non-member requires an available seat within your organization." + }, + "sponsoredFamiliesRemoveActiveSponsorship": { + "message": "When you remove an active sponsorship, a seat within your organization will be available after the renewal date of the sponsored organization." + }, "sponsoredFamiliesEligible": { "message": "Zu eta zure familia hautagai zarete Free Bitwarden Familia-rentzat. Trukatu zure email pertsonalarekin zure datuak modu seguruan." }, @@ -6320,6 +6329,18 @@ "sponsoredFamiliesSharedCollections": { "message": "Familiako sekretuen bilduma partekatuak" }, + "memberFamilies": { + "message": "Member families" + }, + "noMemberFamilies": { + "message": "No member families" + }, + "noMemberFamiliesDescription": { + "message": "Members who have redeemed family plans will display here" + }, + "membersWithSponsoredFamilies": { + "message": "Members of your organization are eligible for Free Bitwarden Families. Here you can see members who have sponsored a Families organization." + }, "badToken": { "message": "Lotura ez da baliozkoa. Mesedez, eskatu babesleari eskaintza birbidaltzeko." }, @@ -7249,6 +7270,9 @@ "followTheStepsBelowToFinishLoggingIn": { "message": "Follow the steps below to finish logging in." }, + "followTheStepsBelowToFinishLoggingInWithSecurityKey": { + "message": "Follow the steps below to finish logging in with your security key." + }, "launchDuo": { "message": "Launch Duo" }, @@ -7983,6 +8007,9 @@ "inviteMember": { "message": "Invite member" }, + "addSponsorship": { + "message": "Add sponsorship" + }, "needsConfirmation": { "message": "Needs confirmation" }, @@ -10571,20 +10598,11 @@ "removeUnlockWithPinPolicyDesc": { "message": "Do not allow members to unlock their account with a PIN." }, - "limitedEventLogs": { - "message": "$PRODUCT_TYPE$ plans do not have access to real event logs", - "placeholders": { - "product_type": { - "content": "$1", - "example": "Teams" - } - } + "upgradeForFullEventsMessage": { + "message": "Event logs are not stored for your organization. Upgrade to a Teams or Enterprise plan to get full access to organization event logs." }, - "upgradeForFullEvents": { - "message": "Get full access to organization event logs by upgrading to a Teams or Enterprise plan." - }, - "upgradeEventLogTitle": { - "message": "Upgrade for real event log data" + "upgradeEventLogTitleMessage": { + "message": "Upgrade to see event logs from your organization." }, "upgradeEventLogMessage": { "message": "These events are examples only and do not reflect real events within your Bitwarden organization." @@ -10600,5 +10618,8 @@ }, "newBusinessUnit": { "message": "New business unit" + }, + "restart": { + "message": "Restart" } } diff --git a/apps/web/src/locales/fa/messages.json b/apps/web/src/locales/fa/messages.json index 5e37f707f0c..b0b98e0a1c3 100644 --- a/apps/web/src/locales/fa/messages.json +++ b/apps/web/src/locales/fa/messages.json @@ -2,6 +2,9 @@ "allApplications": { "message": "All applications" }, + "appLogoLabel": { + "message": "Bitwarden logo" + }, "criticalApplications": { "message": "Critical applications" }, @@ -419,18 +422,9 @@ "folder": { "message": "پوشه" }, - "newCustomField": { - "message": "فیلد سفارشی جدید" - }, "value": { "message": "مقدار" }, - "dragToSort": { - "message": "برای مرتبسازی بکشید" - }, - "dragToReorder": { - "message": "Drag to reorder" - }, "cfTypeText": { "message": "متن" }, @@ -4072,8 +4066,8 @@ "updateBrowser": { "message": "بهروزرسانی مرورگر" }, - "generatingRiskInsights": { - "message": "Generating your risk insights..." + "generatingYourRiskInsights": { + "message": "Generating your Risk Insights..." }, "updateBrowserDesc": { "message": "شما از یک مرورگر وب پشتیبانی نشده استفاده میکنید. گاوصندوق وب ممکن است به درستی کار نکند." @@ -6305,6 +6299,21 @@ "sponsoredFamilies": { "message": "خانوادههای Bitwarden رایگان" }, + "sponsoredBitwardenFamilies": { + "message": "Sponsored families" + }, + "noSponsoredFamilies": { + "message": "No sponsored families" + }, + "noSponsoredFamiliesDescription": { + "message": "Sponsored non-member families plans will display here" + }, + "sponsorFreeBitwardenFamilies": { + "message": "Members of your organization are eligible for Free Bitwarden Families. You can sponsor Free Bitwarden Families for employees who are not a member of your Bitwarden organization. Sponsoring a non-member requires an available seat within your organization." + }, + "sponsoredFamiliesRemoveActiveSponsorship": { + "message": "When you remove an active sponsorship, a seat within your organization will be available after the renewal date of the sponsored organization." + }, "sponsoredFamiliesEligible": { "message": "شما و خانوادهتان واجد شرایط دریافت خانوادههای Bitwarden رایگان هستید. با ایمیل شخصی خود بازخرید کنید تا اطلاعات خود را حتی زمانی که در محل کار نیستید ایمن نگه دارید." }, @@ -6320,6 +6329,18 @@ "sponsoredFamiliesSharedCollections": { "message": "مجموعههای مشترک برای اسرار خانواده" }, + "memberFamilies": { + "message": "Member families" + }, + "noMemberFamilies": { + "message": "No member families" + }, + "noMemberFamiliesDescription": { + "message": "Members who have redeemed family plans will display here" + }, + "membersWithSponsoredFamilies": { + "message": "Members of your organization are eligible for Free Bitwarden Families. Here you can see members who have sponsored a Families organization." + }, "badToken": { "message": "پیوند دیگر معتبر نیست. لطفاً از حمایت کننده بخواهید پیشنهاد را مجدداً ارسال کند." }, @@ -7249,6 +7270,9 @@ "followTheStepsBelowToFinishLoggingIn": { "message": "Follow the steps below to finish logging in." }, + "followTheStepsBelowToFinishLoggingInWithSecurityKey": { + "message": "Follow the steps below to finish logging in with your security key." + }, "launchDuo": { "message": "Launch Duo" }, @@ -7983,6 +8007,9 @@ "inviteMember": { "message": "دعوت از عضو" }, + "addSponsorship": { + "message": "Add sponsorship" + }, "needsConfirmation": { "message": "نیاز به تأیید دارد" }, @@ -10571,20 +10598,11 @@ "removeUnlockWithPinPolicyDesc": { "message": "Do not allow members to unlock their account with a PIN." }, - "limitedEventLogs": { - "message": "$PRODUCT_TYPE$ plans do not have access to real event logs", - "placeholders": { - "product_type": { - "content": "$1", - "example": "Teams" - } - } + "upgradeForFullEventsMessage": { + "message": "Event logs are not stored for your organization. Upgrade to a Teams or Enterprise plan to get full access to organization event logs." }, - "upgradeForFullEvents": { - "message": "Get full access to organization event logs by upgrading to a Teams or Enterprise plan." - }, - "upgradeEventLogTitle": { - "message": "Upgrade for real event log data" + "upgradeEventLogTitleMessage": { + "message": "Upgrade to see event logs from your organization." }, "upgradeEventLogMessage": { "message": "These events are examples only and do not reflect real events within your Bitwarden organization." @@ -10600,5 +10618,8 @@ }, "newBusinessUnit": { "message": "New business unit" + }, + "restart": { + "message": "Restart" } } diff --git a/apps/web/src/locales/fi/messages.json b/apps/web/src/locales/fi/messages.json index b5b5108aa14..2f0dad8001c 100644 --- a/apps/web/src/locales/fi/messages.json +++ b/apps/web/src/locales/fi/messages.json @@ -2,6 +2,9 @@ "allApplications": { "message": "Kaikki sovellukset" }, + "appLogoLabel": { + "message": "Bitwarden-logo" + }, "criticalApplications": { "message": "Kriittiset sovellukset" }, @@ -419,18 +422,9 @@ "folder": { "message": "Kansio" }, - "newCustomField": { - "message": "Uusi lisäkenttä" - }, "value": { "message": "Arvo" }, - "dragToSort": { - "message": "Järjestele raahaamalla" - }, - "dragToReorder": { - "message": "Järjestä vetämällä" - }, "cfTypeText": { "message": "Teksti" }, @@ -1397,7 +1391,7 @@ "message": "Unlock Bitwarden on your device or on the " }, "areYouTryingToAccessYourAccount": { - "message": "Are you trying to access your account?" + "message": "Yritätkö käyttää tiliäsi?" }, "accessAttemptBy": { "message": "Access attempt by $EMAIL$", @@ -4072,8 +4066,8 @@ "updateBrowser": { "message": "Päivitä selain" }, - "generatingRiskInsights": { - "message": "Generating your risk insights..." + "generatingYourRiskInsights": { + "message": "Generating your Risk Insights..." }, "updateBrowserDesc": { "message": "Käytät selainta, jota ei tueta. Verkkoholvi ei välttämättä toimi oikein." @@ -6305,6 +6299,21 @@ "sponsoredFamilies": { "message": "Ilmainen Bitwarden Families" }, + "sponsoredBitwardenFamilies": { + "message": "Sponsored families" + }, + "noSponsoredFamilies": { + "message": "No sponsored families" + }, + "noSponsoredFamiliesDescription": { + "message": "Sponsored non-member families plans will display here" + }, + "sponsorFreeBitwardenFamilies": { + "message": "Members of your organization are eligible for Free Bitwarden Families. You can sponsor Free Bitwarden Families for employees who are not a member of your Bitwarden organization. Sponsoring a non-member requires an available seat within your organization." + }, + "sponsoredFamiliesRemoveActiveSponsorship": { + "message": "When you remove an active sponsorship, a seat within your organization will be available after the renewal date of the sponsored organization." + }, "sponsoredFamiliesEligible": { "message": "Sinä ja perheesi olette oikeutettuja ilmaiseen Bitwarden Families -tilaukseen. Lunasta tarjous henkilökohtaisella sähköpostiosoitteellasi suojataksesi tietosi myös töiden ulkopuolella." }, @@ -6320,6 +6329,18 @@ "sponsoredFamiliesSharedCollections": { "message": "Jaetut kokoelmat perheen salaisuuksille" }, + "memberFamilies": { + "message": "Member families" + }, + "noMemberFamilies": { + "message": "No member families" + }, + "noMemberFamiliesDescription": { + "message": "Members who have redeemed family plans will display here" + }, + "membersWithSponsoredFamilies": { + "message": "Members of your organization are eligible for Free Bitwarden Families. Here you can see members who have sponsored a Families organization." + }, "badToken": { "message": "Linkki ei ole enää voimassa. Pyydä sponsoria lähettämään tarjous uudelleen." }, @@ -7249,6 +7270,9 @@ "followTheStepsBelowToFinishLoggingIn": { "message": "Follow the steps below to finish logging in." }, + "followTheStepsBelowToFinishLoggingInWithSecurityKey": { + "message": "Follow the steps below to finish logging in with your security key." + }, "launchDuo": { "message": "Avaa Duo" }, @@ -7983,6 +8007,9 @@ "inviteMember": { "message": "Kutsu jäsen" }, + "addSponsorship": { + "message": "Add sponsorship" + }, "needsConfirmation": { "message": "Vahvistettavat" }, @@ -9333,10 +9360,10 @@ "message": "Configure device management for Bitwarden using the implementation guide for your platform." }, "deviceIdMissing": { - "message": "Device ID is missing" + "message": "Laitteen ID puuttuu" }, "deviceTypeMissing": { - "message": "Device type is missing" + "message": "Laitteen tyyppi puuttuu" }, "deviceCreationDateMissing": { "message": "Laitteen luontipäivä puuttuu" @@ -10225,7 +10252,7 @@ "message": "Muistuta myöhemmin" }, "newDeviceVerificationNoticePageOneFormContent": { - "message": "Do you have reliable access to your email, $EMAIL$?", + "message": "Onko sinulla luotettava pääsy sähköpostiisi, $EMAIL$?", "placeholders": { "email": { "content": "$1", @@ -10234,10 +10261,10 @@ } }, "newDeviceVerificationNoticePageOneEmailAccessNo": { - "message": "No, I do not" + "message": "Ei ole" }, "newDeviceVerificationNoticePageOneEmailAccessYes": { - "message": "Yes, I can reliably access my email" + "message": "Kyllä, voin käyttää sähköpostiani luotettavasti" }, "turnOnTwoStepLogin": { "message": "Ota kaksivaiheinen kirjautuminen käyttöön" @@ -10349,16 +10376,16 @@ "message": "Your master password and encryption keys have been updated. Your other devices have been logged out." }, "trustUserEmergencyAccess": { - "message": "Trust and confirm user" + "message": "Luota ja vahvista käyttäjä" }, "trustOrganization": { - "message": "Trust organization" + "message": "Luota organisaatioon" }, "trust": { - "message": "Trust" + "message": "Luota" }, "doNotTrust": { - "message": "Do not trust" + "message": "Älä luota" }, "emergencyAccessTrustWarning": { "message": "For the security of your account, only confirm if you have granted emergency access to this user and their fingerprint matches what is displayed in their account" @@ -10367,7 +10394,7 @@ "message": "For the security of your account, only proceed if you are a member of this organization, have account recovery enabled, and the fingerprint displayed below matches the organization's fingerprint." }, "trustUser": { - "message": "Trust user" + "message": "Luota käyttäjään" }, "sshKeyWrongPassword": { "message": "Syöttämäsi salasana on virheellinen." @@ -10571,20 +10598,11 @@ "removeUnlockWithPinPolicyDesc": { "message": "Do not allow members to unlock their account with a PIN." }, - "limitedEventLogs": { - "message": "$PRODUCT_TYPE$ plans do not have access to real event logs", - "placeholders": { - "product_type": { - "content": "$1", - "example": "Teams" - } - } + "upgradeForFullEventsMessage": { + "message": "Event logs are not stored for your organization. Upgrade to a Teams or Enterprise plan to get full access to organization event logs." }, - "upgradeForFullEvents": { - "message": "Get full access to organization event logs by upgrading to a Teams or Enterprise plan." - }, - "upgradeEventLogTitle": { - "message": "Upgrade for real event log data" + "upgradeEventLogTitleMessage": { + "message": "Upgrade to see event logs from your organization." }, "upgradeEventLogMessage": { "message": "These events are examples only and do not reflect real events within your Bitwarden organization." @@ -10600,5 +10618,8 @@ }, "newBusinessUnit": { "message": "New business unit" + }, + "restart": { + "message": "Restart" } } diff --git a/apps/web/src/locales/fil/messages.json b/apps/web/src/locales/fil/messages.json index 04f97960ccf..703fad583f7 100644 --- a/apps/web/src/locales/fil/messages.json +++ b/apps/web/src/locales/fil/messages.json @@ -2,6 +2,9 @@ "allApplications": { "message": "All applications" }, + "appLogoLabel": { + "message": "Bitwarden logo" + }, "criticalApplications": { "message": "Critical applications" }, @@ -419,18 +422,9 @@ "folder": { "message": "Folder" }, - "newCustomField": { - "message": "Bagong pasadyang field" - }, "value": { "message": "Halaga" }, - "dragToSort": { - "message": "Hilahin para pagsunud-sunurin" - }, - "dragToReorder": { - "message": "Drag to reorder" - }, "cfTypeText": { "message": "Teksto" }, @@ -4072,8 +4066,8 @@ "updateBrowser": { "message": "Update sa browser" }, - "generatingRiskInsights": { - "message": "Generating your risk insights..." + "generatingYourRiskInsights": { + "message": "Generating your Risk Insights..." }, "updateBrowserDesc": { "message": "Gumagamit ka ng isang hindi suportado na web browser. Ang web vault ay maaaring hindi gumana nang maayos." @@ -6305,6 +6299,21 @@ "sponsoredFamilies": { "message": "Libreng Mga Pamilya ng Bitwarden" }, + "sponsoredBitwardenFamilies": { + "message": "Sponsored families" + }, + "noSponsoredFamilies": { + "message": "No sponsored families" + }, + "noSponsoredFamiliesDescription": { + "message": "Sponsored non-member families plans will display here" + }, + "sponsorFreeBitwardenFamilies": { + "message": "Members of your organization are eligible for Free Bitwarden Families. You can sponsor Free Bitwarden Families for employees who are not a member of your Bitwarden organization. Sponsoring a non-member requires an available seat within your organization." + }, + "sponsoredFamiliesRemoveActiveSponsorship": { + "message": "When you remove an active sponsorship, a seat within your organization will be available after the renewal date of the sponsored organization." + }, "sponsoredFamiliesEligible": { "message": "Ikaw at ang iyong pamilya ay karapat dapat para sa Free Bitwarden Families. Tubusin gamit ang iyong personal na email upang mapanatili ang iyong data na ligtas kahit na wala ka sa trabaho." }, @@ -6320,6 +6329,18 @@ "sponsoredFamiliesSharedCollections": { "message": "Ibinahagi ang mga koleksyon para sa mga lihim ng Pamilya" }, + "memberFamilies": { + "message": "Member families" + }, + "noMemberFamilies": { + "message": "No member families" + }, + "noMemberFamiliesDescription": { + "message": "Members who have redeemed family plans will display here" + }, + "membersWithSponsoredFamilies": { + "message": "Members of your organization are eligible for Free Bitwarden Families. Here you can see members who have sponsored a Families organization." + }, "badToken": { "message": "Hindi na valid ang link. Paki resend na lang sa sponsor ang offer." }, @@ -7249,6 +7270,9 @@ "followTheStepsBelowToFinishLoggingIn": { "message": "Follow the steps below to finish logging in." }, + "followTheStepsBelowToFinishLoggingInWithSecurityKey": { + "message": "Follow the steps below to finish logging in with your security key." + }, "launchDuo": { "message": "Launch Duo" }, @@ -7983,6 +8007,9 @@ "inviteMember": { "message": "Mag imbita ng miyembro" }, + "addSponsorship": { + "message": "Add sponsorship" + }, "needsConfirmation": { "message": "Kailangan ng kumpirmasyon" }, @@ -10571,20 +10598,11 @@ "removeUnlockWithPinPolicyDesc": { "message": "Do not allow members to unlock their account with a PIN." }, - "limitedEventLogs": { - "message": "$PRODUCT_TYPE$ plans do not have access to real event logs", - "placeholders": { - "product_type": { - "content": "$1", - "example": "Teams" - } - } + "upgradeForFullEventsMessage": { + "message": "Event logs are not stored for your organization. Upgrade to a Teams or Enterprise plan to get full access to organization event logs." }, - "upgradeForFullEvents": { - "message": "Get full access to organization event logs by upgrading to a Teams or Enterprise plan." - }, - "upgradeEventLogTitle": { - "message": "Upgrade for real event log data" + "upgradeEventLogTitleMessage": { + "message": "Upgrade to see event logs from your organization." }, "upgradeEventLogMessage": { "message": "These events are examples only and do not reflect real events within your Bitwarden organization." @@ -10600,5 +10618,8 @@ }, "newBusinessUnit": { "message": "New business unit" + }, + "restart": { + "message": "Restart" } } diff --git a/apps/web/src/locales/fr/messages.json b/apps/web/src/locales/fr/messages.json index 0f6969aa333..ed5ebdf9f12 100644 --- a/apps/web/src/locales/fr/messages.json +++ b/apps/web/src/locales/fr/messages.json @@ -2,6 +2,9 @@ "allApplications": { "message": "Toutes les applications" }, + "appLogoLabel": { + "message": "Logo de Bitwarden" + }, "criticalApplications": { "message": "Applications critiques" }, @@ -419,18 +422,9 @@ "folder": { "message": "Dossier" }, - "newCustomField": { - "message": "Nouveau champ personnalisé" - }, "value": { "message": "Valeur" }, - "dragToSort": { - "message": "Glissez pour trier" - }, - "dragToReorder": { - "message": "Faire glisser pour réorganiser" - }, "cfTypeText": { "message": "Texte" }, @@ -4072,8 +4066,8 @@ "updateBrowser": { "message": "Mettre à jour le navigateur" }, - "generatingRiskInsights": { - "message": "Génération de vos connaissances en matière de risque..." + "generatingYourRiskInsights": { + "message": "Génération de vos Aperçus de Risque..." }, "updateBrowserDesc": { "message": "Vous utilisez un navigateur non supporté. Le coffre web pourrait ne pas fonctionner correctement." @@ -6305,6 +6299,21 @@ "sponsoredFamilies": { "message": "Bitwarden Familles gratuit" }, + "sponsoredBitwardenFamilies": { + "message": "Familles parrainées" + }, + "noSponsoredFamilies": { + "message": "Aucune famille parrainée" + }, + "noSponsoredFamiliesDescription": { + "message": "Les abonnements des familles non membres parrainées apparaîtront ici" + }, + "sponsorFreeBitwardenFamilies": { + "message": "Les membres de votre organisation sont éligibles pour les Familles Gratuites de Bitwarden. Vous pouvez parrainer Familles Gratuites de Bitwarden pour les employés qui ne sont pas membres de votre organisation Bitwarden. Parrainer un non-membre nécessite un siège disponible au sein de votre organisation." + }, + "sponsoredFamiliesRemoveActiveSponsorship": { + "message": "Lorsque vous supprimez un parrainage actif, une licence au sein de votre organisation sera disponible après la date de renouvellement de l'organisation parrainée." + }, "sponsoredFamiliesEligible": { "message": "Vous et votre famille êtes éligibles à Bitwarden Familles gratuitement. Réclamez-le avec votre courriel personnel pour sécuriser vos données, même lorsque vous n'êtes pas au travail." }, @@ -6320,6 +6329,18 @@ "sponsoredFamiliesSharedCollections": { "message": "Collections partagées pour les secrets de la famille" }, + "memberFamilies": { + "message": "Familles de membre" + }, + "noMemberFamilies": { + "message": "Familles de non-membre" + }, + "noMemberFamiliesDescription": { + "message": "Les membres qui ont réclamé des plans familiaux s'afficheront ici" + }, + "membersWithSponsoredFamilies": { + "message": "Les membres de votre organisation sont éligibles à l'abonnement aux Familles Gratuites de Bitwarden. Vous pouvez voir ici les membres qui ont parrainé une organisation Familles." + }, "badToken": { "message": "Le lien n'est plus valide. Merci de demander à votre parrain de renvoyer l'offre." }, @@ -7249,6 +7270,9 @@ "followTheStepsBelowToFinishLoggingIn": { "message": "Suivez les étapes ci-dessous pour terminer de vous connecter." }, + "followTheStepsBelowToFinishLoggingInWithSecurityKey": { + "message": "Suivez les étapes ci-dessous pour terminer la connexion avec votre clé de sécurité." + }, "launchDuo": { "message": "Lancer DUO" }, @@ -7983,6 +8007,9 @@ "inviteMember": { "message": "Inviter un membre" }, + "addSponsorship": { + "message": "Ajouter un parrainage" + }, "needsConfirmation": { "message": "Confirmation nécessaire" }, @@ -10571,20 +10598,11 @@ "removeUnlockWithPinPolicyDesc": { "message": "Ne pas autoriser les membres à déverrouiller leur compte avec un NIP." }, - "limitedEventLogs": { - "message": "Les plans $PRODUCT_TYPE$ n'ont pas accès aux journaux d'événements réels", - "placeholders": { - "product_type": { - "content": "$1", - "example": "Teams" - } - } + "upgradeForFullEventsMessage": { + "message": "Event logs are not stored for your organization. Upgrade to a Teams or Enterprise plan to get full access to organization event logs." }, - "upgradeForFullEvents": { - "message": "Obtenez un accès complet aux journaux d'événements de l'organisation en mettant à niveau vers un plan Équipes ou Entreprise." - }, - "upgradeEventLogTitle": { - "message": "Mettez à niveau pour les données du journal des événements réels" + "upgradeEventLogTitleMessage": { + "message": "Upgrade to see event logs from your organization." }, "upgradeEventLogMessage": { "message": "Ces événements sont des exemples et ne reflètent pas les événements réels au sein de votre organisation Bitwarden." @@ -10600,5 +10618,8 @@ }, "newBusinessUnit": { "message": "Nouvelle unité d'affaires" + }, + "restart": { + "message": "Redémarrer" } } diff --git a/apps/web/src/locales/gl/messages.json b/apps/web/src/locales/gl/messages.json index 33aec61c9ad..26fc9925535 100644 --- a/apps/web/src/locales/gl/messages.json +++ b/apps/web/src/locales/gl/messages.json @@ -2,6 +2,9 @@ "allApplications": { "message": "All applications" }, + "appLogoLabel": { + "message": "Bitwarden logo" + }, "criticalApplications": { "message": "Critical applications" }, @@ -419,18 +422,9 @@ "folder": { "message": "Folder" }, - "newCustomField": { - "message": "New custom field" - }, "value": { "message": "Value" }, - "dragToSort": { - "message": "Drag to sort" - }, - "dragToReorder": { - "message": "Drag to reorder" - }, "cfTypeText": { "message": "Text" }, @@ -4072,8 +4066,8 @@ "updateBrowser": { "message": "Update browser" }, - "generatingRiskInsights": { - "message": "Generating your risk insights..." + "generatingYourRiskInsights": { + "message": "Generating your Risk Insights..." }, "updateBrowserDesc": { "message": "You are using an unsupported web browser. The web vault may not function properly." @@ -6305,6 +6299,21 @@ "sponsoredFamilies": { "message": "Free Bitwarden Families" }, + "sponsoredBitwardenFamilies": { + "message": "Sponsored families" + }, + "noSponsoredFamilies": { + "message": "No sponsored families" + }, + "noSponsoredFamiliesDescription": { + "message": "Sponsored non-member families plans will display here" + }, + "sponsorFreeBitwardenFamilies": { + "message": "Members of your organization are eligible for Free Bitwarden Families. You can sponsor Free Bitwarden Families for employees who are not a member of your Bitwarden organization. Sponsoring a non-member requires an available seat within your organization." + }, + "sponsoredFamiliesRemoveActiveSponsorship": { + "message": "When you remove an active sponsorship, a seat within your organization will be available after the renewal date of the sponsored organization." + }, "sponsoredFamiliesEligible": { "message": "You and your family are eligible for Free Bitwarden Families. Redeem with your personal email to keep your data secure even when you are not at work." }, @@ -6320,6 +6329,18 @@ "sponsoredFamiliesSharedCollections": { "message": "Shared collections for Family secrets" }, + "memberFamilies": { + "message": "Member families" + }, + "noMemberFamilies": { + "message": "No member families" + }, + "noMemberFamiliesDescription": { + "message": "Members who have redeemed family plans will display here" + }, + "membersWithSponsoredFamilies": { + "message": "Members of your organization are eligible for Free Bitwarden Families. Here you can see members who have sponsored a Families organization." + }, "badToken": { "message": "The link is no longer valid. Please have the sponsor resend the offer." }, @@ -7249,6 +7270,9 @@ "followTheStepsBelowToFinishLoggingIn": { "message": "Follow the steps below to finish logging in." }, + "followTheStepsBelowToFinishLoggingInWithSecurityKey": { + "message": "Follow the steps below to finish logging in with your security key." + }, "launchDuo": { "message": "Launch Duo" }, @@ -7983,6 +8007,9 @@ "inviteMember": { "message": "Invite member" }, + "addSponsorship": { + "message": "Add sponsorship" + }, "needsConfirmation": { "message": "Needs confirmation" }, @@ -10571,20 +10598,11 @@ "removeUnlockWithPinPolicyDesc": { "message": "Do not allow members to unlock their account with a PIN." }, - "limitedEventLogs": { - "message": "$PRODUCT_TYPE$ plans do not have access to real event logs", - "placeholders": { - "product_type": { - "content": "$1", - "example": "Teams" - } - } + "upgradeForFullEventsMessage": { + "message": "Event logs are not stored for your organization. Upgrade to a Teams or Enterprise plan to get full access to organization event logs." }, - "upgradeForFullEvents": { - "message": "Get full access to organization event logs by upgrading to a Teams or Enterprise plan." - }, - "upgradeEventLogTitle": { - "message": "Upgrade for real event log data" + "upgradeEventLogTitleMessage": { + "message": "Upgrade to see event logs from your organization." }, "upgradeEventLogMessage": { "message": "These events are examples only and do not reflect real events within your Bitwarden organization." @@ -10600,5 +10618,8 @@ }, "newBusinessUnit": { "message": "New business unit" + }, + "restart": { + "message": "Restart" } } diff --git a/apps/web/src/locales/he/messages.json b/apps/web/src/locales/he/messages.json index ee7f464b9fd..e4e12a6d4f1 100644 --- a/apps/web/src/locales/he/messages.json +++ b/apps/web/src/locales/he/messages.json @@ -2,6 +2,9 @@ "allApplications": { "message": "כל היישומים" }, + "appLogoLabel": { + "message": "Bitwarden logo" + }, "criticalApplications": { "message": "יישומים קריטיים" }, @@ -419,18 +422,9 @@ "folder": { "message": "תיקייה" }, - "newCustomField": { - "message": "שדה מותאם אישית חדש" - }, "value": { "message": "ערך" }, - "dragToSort": { - "message": "גרור כדי למיין" - }, - "dragToReorder": { - "message": "גרור כדי לסדר מחדש" - }, "cfTypeText": { "message": "טקסט" }, @@ -4072,8 +4066,8 @@ "updateBrowser": { "message": "עדכן דפדפן" }, - "generatingRiskInsights": { - "message": "יוצר את תובנות הסיכון שלך..." + "generatingYourRiskInsights": { + "message": "Generating your Risk Insights..." }, "updateBrowserDesc": { "message": "אתה משתמש בדפדפן אינטרנט שאיננו נתמך. כספת הרשת עלולה שלא לפעול כראוי." @@ -6305,6 +6299,21 @@ "sponsoredFamilies": { "message": "Bitwarden למשפחות בחינם" }, + "sponsoredBitwardenFamilies": { + "message": "Sponsored families" + }, + "noSponsoredFamilies": { + "message": "No sponsored families" + }, + "noSponsoredFamiliesDescription": { + "message": "Sponsored non-member families plans will display here" + }, + "sponsorFreeBitwardenFamilies": { + "message": "Members of your organization are eligible for Free Bitwarden Families. You can sponsor Free Bitwarden Families for employees who are not a member of your Bitwarden organization. Sponsoring a non-member requires an available seat within your organization." + }, + "sponsoredFamiliesRemoveActiveSponsorship": { + "message": "When you remove an active sponsorship, a seat within your organization will be available after the renewal date of the sponsored organization." + }, "sponsoredFamiliesEligible": { "message": "אתה והמשפחה שלך זכאים ל־Bitwarden למשפחות בחינם. ממש עם הדוא\"ל האישי שלך כדי לשמור על אבטחת הנתונים שלך אפילו כשאתה לא בעבודה." }, @@ -6320,6 +6329,18 @@ "sponsoredFamiliesSharedCollections": { "message": "אוספים משותפים עבור סודות משפחה" }, + "memberFamilies": { + "message": "Member families" + }, + "noMemberFamilies": { + "message": "No member families" + }, + "noMemberFamiliesDescription": { + "message": "Members who have redeemed family plans will display here" + }, + "membersWithSponsoredFamilies": { + "message": "Members of your organization are eligible for Free Bitwarden Families. Here you can see members who have sponsored a Families organization." + }, "badToken": { "message": "הקישור אינו חוקי עוד. אנא בקש מנותן החסות לשלוח שוב את ההצעה." }, @@ -7249,6 +7270,9 @@ "followTheStepsBelowToFinishLoggingIn": { "message": "עקוב אחר השלבים למטה כדי לסיים להיכנס." }, + "followTheStepsBelowToFinishLoggingInWithSecurityKey": { + "message": "Follow the steps below to finish logging in with your security key." + }, "launchDuo": { "message": "פתח את Duo" }, @@ -7983,6 +8007,9 @@ "inviteMember": { "message": "הזמן חבר" }, + "addSponsorship": { + "message": "Add sponsorship" + }, "needsConfirmation": { "message": "צריך אישור" }, @@ -10571,20 +10598,11 @@ "removeUnlockWithPinPolicyDesc": { "message": "אל תאפשר לחברים לבטל את נעילת החשבון שלהם עם PIN." }, - "limitedEventLogs": { - "message": "לתוכניות מסוג $PRODUCT_TYPE$ אין גישה ליומני אירועים אמיתיים", - "placeholders": { - "product_type": { - "content": "$1", - "example": "Teams" - } - } + "upgradeForFullEventsMessage": { + "message": "Event logs are not stored for your organization. Upgrade to a Teams or Enterprise plan to get full access to organization event logs." }, - "upgradeForFullEvents": { - "message": "קבל גישה מלאה ליומני אירועים של ארגון על ידי שדרוג לתוכנית לצוותים או ארגונים." - }, - "upgradeEventLogTitle": { - "message": "שדרג עבור נתוני יומן אירועים אמיתיים" + "upgradeEventLogTitleMessage": { + "message": "Upgrade to see event logs from your organization." }, "upgradeEventLogMessage": { "message": "האירועים האלה הם דוגמאות בלבד ולא משקפים אירועים אמיתיים בתוך ארגון ה־Bitwarden שלך." @@ -10600,5 +10618,8 @@ }, "newBusinessUnit": { "message": "New business unit" + }, + "restart": { + "message": "Restart" } } diff --git a/apps/web/src/locales/hi/messages.json b/apps/web/src/locales/hi/messages.json index 2ffc414866e..2db2d9c92b1 100644 --- a/apps/web/src/locales/hi/messages.json +++ b/apps/web/src/locales/hi/messages.json @@ -2,6 +2,9 @@ "allApplications": { "message": "All applications" }, + "appLogoLabel": { + "message": "Bitwarden logo" + }, "criticalApplications": { "message": "Critical applications" }, @@ -419,18 +422,9 @@ "folder": { "message": "फ़ोल्डर" }, - "newCustomField": { - "message": "नया कस्टम फील्ड" - }, "value": { "message": "मूल्य" }, - "dragToSort": { - "message": "सॉर्ट करने के लिए ड्रैग करें" - }, - "dragToReorder": { - "message": "Drag to reorder" - }, "cfTypeText": { "message": "शब्द" }, @@ -4072,8 +4066,8 @@ "updateBrowser": { "message": "Update browser" }, - "generatingRiskInsights": { - "message": "Generating your risk insights..." + "generatingYourRiskInsights": { + "message": "Generating your Risk Insights..." }, "updateBrowserDesc": { "message": "You are using an unsupported web browser. The web vault may not function properly." @@ -6305,6 +6299,21 @@ "sponsoredFamilies": { "message": "Free Bitwarden Families" }, + "sponsoredBitwardenFamilies": { + "message": "Sponsored families" + }, + "noSponsoredFamilies": { + "message": "No sponsored families" + }, + "noSponsoredFamiliesDescription": { + "message": "Sponsored non-member families plans will display here" + }, + "sponsorFreeBitwardenFamilies": { + "message": "Members of your organization are eligible for Free Bitwarden Families. You can sponsor Free Bitwarden Families for employees who are not a member of your Bitwarden organization. Sponsoring a non-member requires an available seat within your organization." + }, + "sponsoredFamiliesRemoveActiveSponsorship": { + "message": "When you remove an active sponsorship, a seat within your organization will be available after the renewal date of the sponsored organization." + }, "sponsoredFamiliesEligible": { "message": "You and your family are eligible for Free Bitwarden Families. Redeem with your personal email to keep your data secure even when you are not at work." }, @@ -6320,6 +6329,18 @@ "sponsoredFamiliesSharedCollections": { "message": "Shared collections for Family secrets" }, + "memberFamilies": { + "message": "Member families" + }, + "noMemberFamilies": { + "message": "No member families" + }, + "noMemberFamiliesDescription": { + "message": "Members who have redeemed family plans will display here" + }, + "membersWithSponsoredFamilies": { + "message": "Members of your organization are eligible for Free Bitwarden Families. Here you can see members who have sponsored a Families organization." + }, "badToken": { "message": "The link is no longer valid. Please have the sponsor resend the offer." }, @@ -7249,6 +7270,9 @@ "followTheStepsBelowToFinishLoggingIn": { "message": "Follow the steps below to finish logging in." }, + "followTheStepsBelowToFinishLoggingInWithSecurityKey": { + "message": "Follow the steps below to finish logging in with your security key." + }, "launchDuo": { "message": "Launch Duo" }, @@ -7983,6 +8007,9 @@ "inviteMember": { "message": "Invite member" }, + "addSponsorship": { + "message": "Add sponsorship" + }, "needsConfirmation": { "message": "Needs confirmation" }, @@ -10571,20 +10598,11 @@ "removeUnlockWithPinPolicyDesc": { "message": "Do not allow members to unlock their account with a PIN." }, - "limitedEventLogs": { - "message": "$PRODUCT_TYPE$ plans do not have access to real event logs", - "placeholders": { - "product_type": { - "content": "$1", - "example": "Teams" - } - } + "upgradeForFullEventsMessage": { + "message": "Event logs are not stored for your organization. Upgrade to a Teams or Enterprise plan to get full access to organization event logs." }, - "upgradeForFullEvents": { - "message": "Get full access to organization event logs by upgrading to a Teams or Enterprise plan." - }, - "upgradeEventLogTitle": { - "message": "Upgrade for real event log data" + "upgradeEventLogTitleMessage": { + "message": "Upgrade to see event logs from your organization." }, "upgradeEventLogMessage": { "message": "These events are examples only and do not reflect real events within your Bitwarden organization." @@ -10600,5 +10618,8 @@ }, "newBusinessUnit": { "message": "New business unit" + }, + "restart": { + "message": "Restart" } } diff --git a/apps/web/src/locales/hr/messages.json b/apps/web/src/locales/hr/messages.json index e5121050ed8..32179a09d16 100644 --- a/apps/web/src/locales/hr/messages.json +++ b/apps/web/src/locales/hr/messages.json @@ -2,6 +2,9 @@ "allApplications": { "message": "Sve aplikacije" }, + "appLogoLabel": { + "message": "Bitwarden logo" + }, "criticalApplications": { "message": "Kritične aplikacije" }, @@ -419,18 +422,9 @@ "folder": { "message": "Mapa" }, - "newCustomField": { - "message": "Novo prilagođeno polje" - }, "value": { "message": "Vrijednost" }, - "dragToSort": { - "message": "Povuci za sortiranje" - }, - "dragToReorder": { - "message": "Povuci za premještanje" - }, "cfTypeText": { "message": "Tekst" }, @@ -4072,8 +4066,8 @@ "updateBrowser": { "message": "Ažuriraj preglednik" }, - "generatingRiskInsights": { - "message": "Stvaranje tvojih uvida u rizik..." + "generatingYourRiskInsights": { + "message": "Generating your Risk Insights..." }, "updateBrowserDesc": { "message": "Koristiš nepodržani preglednik. Web trezor možda neće ispravno raditi." @@ -6305,6 +6299,21 @@ "sponsoredFamilies": { "message": "Besplatan Bitwarden Families" }, + "sponsoredBitwardenFamilies": { + "message": "Sponsored families" + }, + "noSponsoredFamilies": { + "message": "No sponsored families" + }, + "noSponsoredFamiliesDescription": { + "message": "Sponsored non-member families plans will display here" + }, + "sponsorFreeBitwardenFamilies": { + "message": "Members of your organization are eligible for Free Bitwarden Families. You can sponsor Free Bitwarden Families for employees who are not a member of your Bitwarden organization. Sponsoring a non-member requires an available seat within your organization." + }, + "sponsoredFamiliesRemoveActiveSponsorship": { + "message": "When you remove an active sponsorship, a seat within your organization will be available after the renewal date of the sponsored organization." + }, "sponsoredFamiliesEligible": { "message": "Ti i tvoja obitelj ispunjavate uvjete za besplatni Bitwarden Families. Iskoristiti ponudu svojom e-poštom kako bi zaštitio svoje podatke čak i kada nisi na poslu." }, @@ -6320,6 +6329,18 @@ "sponsoredFamiliesSharedCollections": { "message": "Dijeljene zbirke za obiteljske tajne" }, + "memberFamilies": { + "message": "Member families" + }, + "noMemberFamilies": { + "message": "No member families" + }, + "noMemberFamiliesDescription": { + "message": "Members who have redeemed family plans will display here" + }, + "membersWithSponsoredFamilies": { + "message": "Members of your organization are eligible for Free Bitwarden Families. Here you can see members who have sponsored a Families organization." + }, "badToken": { "message": "Veza više nije valjana. Zamoli sponzora da pošalje novu ponudu." }, @@ -7249,6 +7270,9 @@ "followTheStepsBelowToFinishLoggingIn": { "message": "Prati korake za dovršetak prijave." }, + "followTheStepsBelowToFinishLoggingInWithSecurityKey": { + "message": "Follow the steps below to finish logging in with your security key." + }, "launchDuo": { "message": "Pokreni Duo" }, @@ -7983,6 +8007,9 @@ "inviteMember": { "message": "Pozovi člana" }, + "addSponsorship": { + "message": "Add sponsorship" + }, "needsConfirmation": { "message": "Treba potvrdu" }, @@ -10571,20 +10598,11 @@ "removeUnlockWithPinPolicyDesc": { "message": "Ne dozvoli članovima otključavanje računa PIN-om." }, - "limitedEventLogs": { - "message": "$PRODUCT_TYPE$ planovi nemaju pristup stvarnim zapisima događaja", - "placeholders": { - "product_type": { - "content": "$1", - "example": "Teams" - } - } + "upgradeForFullEventsMessage": { + "message": "Event logs are not stored for your organization. Upgrade to a Teams or Enterprise plan to get full access to organization event logs." }, - "upgradeForFullEvents": { - "message": "Omogući puni pristup zapisnicima događaja organizacije nadogradnjom na plan Teams ili Enterprise." - }, - "upgradeEventLogTitle": { - "message": "Nadogradi za stvarne podatke dnevnika događaja" + "upgradeEventLogTitleMessage": { + "message": "Upgrade to see event logs from your organization." }, "upgradeEventLogMessage": { "message": "Ovi događaji su samo primjeri i ne odražavaju stvarne događaje unutar tvoje Bitwarden organizacije." @@ -10600,5 +10618,8 @@ }, "newBusinessUnit": { "message": "New business unit" + }, + "restart": { + "message": "Restart" } } diff --git a/apps/web/src/locales/hu/messages.json b/apps/web/src/locales/hu/messages.json index 266783e836f..ac0cdb58036 100644 --- a/apps/web/src/locales/hu/messages.json +++ b/apps/web/src/locales/hu/messages.json @@ -2,6 +2,9 @@ "allApplications": { "message": "Összes alkalmazás" }, + "appLogoLabel": { + "message": "Bitwarden logó" + }, "criticalApplications": { "message": "Kritikus alkalmazások" }, @@ -419,18 +422,9 @@ "folder": { "message": "Mappa" }, - "newCustomField": { - "message": "Új egyedi mező" - }, "value": { "message": "Érték" }, - "dragToSort": { - "message": "Húzás a rendezéshez" - }, - "dragToReorder": { - "message": "Átrendezés áthúzással" - }, "cfTypeText": { "message": "Szöveg" }, @@ -4072,7 +4066,7 @@ "updateBrowser": { "message": "Böngésző frissítése" }, - "generatingRiskInsights": { + "generatingYourRiskInsights": { "message": "A kockázati betekintések generálása..." }, "updateBrowserDesc": { @@ -6305,6 +6299,21 @@ "sponsoredFamilies": { "message": "Díjmentes Bitwarden családi csomag" }, + "sponsoredBitwardenFamilies": { + "message": "Szponzorált családok" + }, + "noSponsoredFamilies": { + "message": "Nincsenek szponzorált családok." + }, + "noSponsoredFamiliesDescription": { + "message": "A szponzorált, nem-tag családok csomagjai itt jelennek meg." + }, + "sponsorFreeBitwardenFamilies": { + "message": "A szervezet tagjai jogosultak a Free Bitwarden Families programra. Ingyenes Bitwarden Családokat szponzorálhat olyan alkalmazottak számára, akik nem tagjai Bitwarden szervezetének. A nem-tag szponzorálásához a szervezeten belül rendelkezésre álló hely szükséges." + }, + "sponsoredFamiliesRemoveActiveSponsorship": { + "message": "Amikor eltávolítunk egy aktív szponzorációt, a szponzorált szervezet megújítási dátuma után elérhető lesz egy hely a szervezeten belül." + }, "sponsoredFamiliesEligible": { "message": "A felhasználó és családja jogosult az ingyenes Bitwarden családok programra. Váltsuk ezt be személyes email címmel, hogy az adatok biztonságban legyenek még akkor is, amikor éppen nem dolgozunk." }, @@ -6320,6 +6329,18 @@ "sponsoredFamiliesSharedCollections": { "message": "Megosztott gyűjtemények a családi titkoknak" }, + "memberFamilies": { + "message": "Tag családok" + }, + "noMemberFamilies": { + "message": "Nincsenek tag családok." + }, + "noMemberFamiliesDescription": { + "message": "Itt jelennek meg azok a tagok, akik beváltották a családi csomagokat." + }, + "membersWithSponsoredFamilies": { + "message": "A szervezet tagjai jogosultak a Free Bitwarden Families programra. Itt láthatjuk azokat a tagokat, akik szponzoráltak egy Családok szervezetet." + }, "badToken": { "message": "A hivatkozás már nem érvényes. Kérjük a szponzortól az ajánlat újraküldését." }, @@ -7249,6 +7270,9 @@ "followTheStepsBelowToFinishLoggingIn": { "message": "Kövessük az alábbi lépéseket a bejelentkezés befejezéséhez." }, + "followTheStepsBelowToFinishLoggingInWithSecurityKey": { + "message": "Kövessük az alábbi lépéseket a biztonsági kulccsal bejelentkezés befejezéséhez." + }, "launchDuo": { "message": "Duo indítása" }, @@ -7983,6 +8007,9 @@ "inviteMember": { "message": "Tag meghívása" }, + "addSponsorship": { + "message": "Szponzorálás hozzáadása" + }, "needsConfirmation": { "message": "Jóváhagyás szükséges" }, @@ -10571,20 +10598,11 @@ "removeUnlockWithPinPolicyDesc": { "message": "Nem engedjük meg a tagoknak, hogy PIN kóddal oldják fel fiókjuk zárolását." }, - "limitedEventLogs": { - "message": "A $PRODUCT_TYPE$ csomagok nem férnek hozzá a valós eseménynaplókhoz.", - "placeholders": { - "product_type": { - "content": "$1", - "example": "Teams" - } - } + "upgradeForFullEventsMessage": { + "message": "A szervezet eseménynaplói nem tárolódnak. Váltsunk Teams vagy Enterprise csomagra, hogy teljes hozzáférést kapjunk a szervezet eseménynaplóihoz." }, - "upgradeForFullEvents": { - "message": "Teljes hozzáférést kaphatunk a szervezeti eseménynaplókhoz, ha Teams vagy Enterprise csomagra térünk át." - }, - "upgradeEventLogTitle": { - "message": "Áttérés valós eseménynapló adatokhoz" + "upgradeEventLogTitleMessage": { + "message": "Frissítsünk, hogy megtekinthessük a szervezet eseménynaplóit." }, "upgradeEventLogMessage": { "message": "Ezek az események csak példák és nem tükröznek valós eseményeket a Bitwarden szervezetén belül." @@ -10600,5 +10618,8 @@ }, "newBusinessUnit": { "message": "Új üzleti egység" + }, + "restart": { + "message": "Újraindítás" } } diff --git a/apps/web/src/locales/id/messages.json b/apps/web/src/locales/id/messages.json index 85d6795e9c1..ce965bd29c9 100644 --- a/apps/web/src/locales/id/messages.json +++ b/apps/web/src/locales/id/messages.json @@ -2,6 +2,9 @@ "allApplications": { "message": "Semua aplikasi" }, + "appLogoLabel": { + "message": "Bitwarden logo" + }, "criticalApplications": { "message": "Critical applications" }, @@ -419,18 +422,9 @@ "folder": { "message": "Direktori" }, - "newCustomField": { - "message": "Kolom Ubahsuai Baru" - }, "value": { "message": "Nilai" }, - "dragToSort": { - "message": "Tarik untuk mengurutkan" - }, - "dragToReorder": { - "message": "Drag to reorder" - }, "cfTypeText": { "message": "Teks" }, @@ -4072,8 +4066,8 @@ "updateBrowser": { "message": "Perbarui Browser" }, - "generatingRiskInsights": { - "message": "Generating your risk insights..." + "generatingYourRiskInsights": { + "message": "Generating your Risk Insights..." }, "updateBrowserDesc": { "message": "Anda menggunakan browser web yang tidak didukung. Kubah web mungkin tidak berfungsi dengan baik." @@ -6305,6 +6299,21 @@ "sponsoredFamilies": { "message": "Bitwarden Families Gratis" }, + "sponsoredBitwardenFamilies": { + "message": "Sponsored families" + }, + "noSponsoredFamilies": { + "message": "No sponsored families" + }, + "noSponsoredFamiliesDescription": { + "message": "Sponsored non-member families plans will display here" + }, + "sponsorFreeBitwardenFamilies": { + "message": "Members of your organization are eligible for Free Bitwarden Families. You can sponsor Free Bitwarden Families for employees who are not a member of your Bitwarden organization. Sponsoring a non-member requires an available seat within your organization." + }, + "sponsoredFamiliesRemoveActiveSponsorship": { + "message": "When you remove an active sponsorship, a seat within your organization will be available after the renewal date of the sponsored organization." + }, "sponsoredFamiliesEligible": { "message": "You and your family are eligible for Free Bitwarden Families. Redeem with your personal email to keep your data secure even when you are not at work." }, @@ -6320,6 +6329,18 @@ "sponsoredFamiliesSharedCollections": { "message": "Shared collections for Family secrets" }, + "memberFamilies": { + "message": "Member families" + }, + "noMemberFamilies": { + "message": "No member families" + }, + "noMemberFamiliesDescription": { + "message": "Members who have redeemed family plans will display here" + }, + "membersWithSponsoredFamilies": { + "message": "Members of your organization are eligible for Free Bitwarden Families. Here you can see members who have sponsored a Families organization." + }, "badToken": { "message": "The link is no longer valid. Please have the sponsor resend the offer." }, @@ -7249,6 +7270,9 @@ "followTheStepsBelowToFinishLoggingIn": { "message": "Follow the steps below to finish logging in." }, + "followTheStepsBelowToFinishLoggingInWithSecurityKey": { + "message": "Follow the steps below to finish logging in with your security key." + }, "launchDuo": { "message": "Launch Duo" }, @@ -7983,6 +8007,9 @@ "inviteMember": { "message": "Invite member" }, + "addSponsorship": { + "message": "Add sponsorship" + }, "needsConfirmation": { "message": "Needs confirmation" }, @@ -10571,20 +10598,11 @@ "removeUnlockWithPinPolicyDesc": { "message": "Do not allow members to unlock their account with a PIN." }, - "limitedEventLogs": { - "message": "$PRODUCT_TYPE$ plans do not have access to real event logs", - "placeholders": { - "product_type": { - "content": "$1", - "example": "Teams" - } - } + "upgradeForFullEventsMessage": { + "message": "Event logs are not stored for your organization. Upgrade to a Teams or Enterprise plan to get full access to organization event logs." }, - "upgradeForFullEvents": { - "message": "Get full access to organization event logs by upgrading to a Teams or Enterprise plan." - }, - "upgradeEventLogTitle": { - "message": "Upgrade for real event log data" + "upgradeEventLogTitleMessage": { + "message": "Upgrade to see event logs from your organization." }, "upgradeEventLogMessage": { "message": "These events are examples only and do not reflect real events within your Bitwarden organization." @@ -10600,5 +10618,8 @@ }, "newBusinessUnit": { "message": "New business unit" + }, + "restart": { + "message": "Restart" } } diff --git a/apps/web/src/locales/it/messages.json b/apps/web/src/locales/it/messages.json index 893eed1b846..a76b4c926d1 100644 --- a/apps/web/src/locales/it/messages.json +++ b/apps/web/src/locales/it/messages.json @@ -2,6 +2,9 @@ "allApplications": { "message": "Tutte le applicazioni" }, + "appLogoLabel": { + "message": "Bitwarden logo" + }, "criticalApplications": { "message": "Applicazioni critiche" }, @@ -419,18 +422,9 @@ "folder": { "message": "Cartella" }, - "newCustomField": { - "message": "Nuovo campo personalizzato" - }, "value": { "message": "Valore" }, - "dragToSort": { - "message": "Trascina per ordinare" - }, - "dragToReorder": { - "message": "Trascina per riordinare" - }, "cfTypeText": { "message": "Testo" }, @@ -4072,8 +4066,8 @@ "updateBrowser": { "message": "Aggiorna browser" }, - "generatingRiskInsights": { - "message": "Generazione delle tue informazioni sui rischi..." + "generatingYourRiskInsights": { + "message": "Generating your Risk Insights..." }, "updateBrowserDesc": { "message": "Stai utilizzando un browser non supportato. La cassaforte web potrebbe non funzionare correttamente." @@ -6305,6 +6299,21 @@ "sponsoredFamilies": { "message": "Bitwarden Families gratis" }, + "sponsoredBitwardenFamilies": { + "message": "Sponsored families" + }, + "noSponsoredFamilies": { + "message": "No sponsored families" + }, + "noSponsoredFamiliesDescription": { + "message": "Sponsored non-member families plans will display here" + }, + "sponsorFreeBitwardenFamilies": { + "message": "Members of your organization are eligible for Free Bitwarden Families. You can sponsor Free Bitwarden Families for employees who are not a member of your Bitwarden organization. Sponsoring a non-member requires an available seat within your organization." + }, + "sponsoredFamiliesRemoveActiveSponsorship": { + "message": "When you remove an active sponsorship, a seat within your organization will be available after the renewal date of the sponsored organization." + }, "sponsoredFamiliesEligible": { "message": "Tu e la tua famiglia siete idonei per Bitwarden Families gratis. Riscatta con la tua email personale per mantenere i tuoi dati al sicuro anche quando non sei a lavoro." }, @@ -6320,6 +6329,18 @@ "sponsoredFamiliesSharedCollections": { "message": "Raccolte condivise per segreti di famiglia" }, + "memberFamilies": { + "message": "Member families" + }, + "noMemberFamilies": { + "message": "No member families" + }, + "noMemberFamiliesDescription": { + "message": "Members who have redeemed family plans will display here" + }, + "membersWithSponsoredFamilies": { + "message": "Members of your organization are eligible for Free Bitwarden Families. Here you can see members who have sponsored a Families organization." + }, "badToken": { "message": "Il link non è più valido. Chiedi allo sponsor di inviare l'offerta di nuovo." }, @@ -7249,6 +7270,9 @@ "followTheStepsBelowToFinishLoggingIn": { "message": "Segui i passaggi qui sotto per completare l'accesso." }, + "followTheStepsBelowToFinishLoggingInWithSecurityKey": { + "message": "Follow the steps below to finish logging in with your security key." + }, "launchDuo": { "message": "Avvia DUO" }, @@ -7983,6 +8007,9 @@ "inviteMember": { "message": "Invita membro" }, + "addSponsorship": { + "message": "Add sponsorship" + }, "needsConfirmation": { "message": "Necessitano conferma" }, @@ -10571,20 +10598,11 @@ "removeUnlockWithPinPolicyDesc": { "message": "Non consentire ai membri di sbloccare il proprio account con un PIN." }, - "limitedEventLogs": { - "message": "I piani $PRODUCT_TYPE$ non hanno accesso ai registri degli eventi reali", - "placeholders": { - "product_type": { - "content": "$1", - "example": "Teams" - } - } + "upgradeForFullEventsMessage": { + "message": "Event logs are not stored for your organization. Upgrade to a Teams or Enterprise plan to get full access to organization event logs." }, - "upgradeForFullEvents": { - "message": "Ottieni pieno accesso ai registri degli eventi dell'organizzazione aggiornando a un piano Teams o Enterprise." - }, - "upgradeEventLogTitle": { - "message": "Aggiorna per i dati del registro eventi reali" + "upgradeEventLogTitleMessage": { + "message": "Upgrade to see event logs from your organization." }, "upgradeEventLogMessage": { "message": "Questi eventi sono solo esempi e non riflettono eventi reali all'interno della tua organizzazione Bitwarden." @@ -10600,5 +10618,8 @@ }, "newBusinessUnit": { "message": "New business unit" + }, + "restart": { + "message": "Restart" } } diff --git a/apps/web/src/locales/ja/messages.json b/apps/web/src/locales/ja/messages.json index b9d113b98cd..58423770d46 100644 --- a/apps/web/src/locales/ja/messages.json +++ b/apps/web/src/locales/ja/messages.json @@ -2,6 +2,9 @@ "allApplications": { "message": "すべてのアプリ" }, + "appLogoLabel": { + "message": "Bitwarden logo" + }, "criticalApplications": { "message": "きわめて重要なアプリ" }, @@ -419,18 +422,9 @@ "folder": { "message": "フォルダー" }, - "newCustomField": { - "message": "新規カスタムフィールド" - }, "value": { "message": "値" }, - "dragToSort": { - "message": "ドラッグして並べ替え" - }, - "dragToReorder": { - "message": "ドラッグして並べ替え" - }, "cfTypeText": { "message": "テキスト" }, @@ -4072,8 +4066,8 @@ "updateBrowser": { "message": "ブラウザを更新" }, - "generatingRiskInsights": { - "message": "リスク分析を生成しています..." + "generatingYourRiskInsights": { + "message": "Generating your Risk Insights..." }, "updateBrowserDesc": { "message": "サポートされていないブラウザを使用しています。ウェブ保管庫が正しく動作しないかもしれません。" @@ -6305,6 +6299,21 @@ "sponsoredFamilies": { "message": "Bitwarden 家族向けプランを無償で" }, + "sponsoredBitwardenFamilies": { + "message": "Sponsored families" + }, + "noSponsoredFamilies": { + "message": "No sponsored families" + }, + "noSponsoredFamiliesDescription": { + "message": "Sponsored non-member families plans will display here" + }, + "sponsorFreeBitwardenFamilies": { + "message": "Members of your organization are eligible for Free Bitwarden Families. You can sponsor Free Bitwarden Families for employees who are not a member of your Bitwarden organization. Sponsoring a non-member requires an available seat within your organization." + }, + "sponsoredFamiliesRemoveActiveSponsorship": { + "message": "When you remove an active sponsorship, a seat within your organization will be available after the renewal date of the sponsored organization." + }, "sponsoredFamiliesEligible": { "message": "あなたとご家族は、 Bitwarden 家族向けプランを無償でご利用いただけます。 個人用のメールアドレスで引き換えて、仕事以外でもデータを安全に保ちましょう。" }, @@ -6320,6 +6329,18 @@ "sponsoredFamiliesSharedCollections": { "message": "家族のシークレットのために共有されたコレクション" }, + "memberFamilies": { + "message": "Member families" + }, + "noMemberFamilies": { + "message": "No member families" + }, + "noMemberFamiliesDescription": { + "message": "Members who have redeemed family plans will display here" + }, + "membersWithSponsoredFamilies": { + "message": "Members of your organization are eligible for Free Bitwarden Families. Here you can see members who have sponsored a Families organization." + }, "badToken": { "message": "リンクは無効になりました。スポンサーにオファーを再送信してもらってください。" }, @@ -7249,6 +7270,9 @@ "followTheStepsBelowToFinishLoggingIn": { "message": "以下の手順に従ってログインを完了してください。" }, + "followTheStepsBelowToFinishLoggingInWithSecurityKey": { + "message": "Follow the steps below to finish logging in with your security key." + }, "launchDuo": { "message": "DUO を起動" }, @@ -7983,6 +8007,9 @@ "inviteMember": { "message": "メンバーを招待する" }, + "addSponsorship": { + "message": "Add sponsorship" + }, "needsConfirmation": { "message": "確認が必要" }, @@ -10571,20 +10598,11 @@ "removeUnlockWithPinPolicyDesc": { "message": "PINによるアカウントのロック解除をメンバーに許可しません。" }, - "limitedEventLogs": { - "message": "$PRODUCT_TYPE$ plans do not have access to real event logs", - "placeholders": { - "product_type": { - "content": "$1", - "example": "Teams" - } - } + "upgradeForFullEventsMessage": { + "message": "Event logs are not stored for your organization. Upgrade to a Teams or Enterprise plan to get full access to organization event logs." }, - "upgradeForFullEvents": { - "message": "Get full access to organization event logs by upgrading to a Teams or Enterprise plan." - }, - "upgradeEventLogTitle": { - "message": "Upgrade for real event log data" + "upgradeEventLogTitleMessage": { + "message": "Upgrade to see event logs from your organization." }, "upgradeEventLogMessage": { "message": "These events are examples only and do not reflect real events within your Bitwarden organization." @@ -10600,5 +10618,8 @@ }, "newBusinessUnit": { "message": "New business unit" + }, + "restart": { + "message": "Restart" } } diff --git a/apps/web/src/locales/ka/messages.json b/apps/web/src/locales/ka/messages.json index ede2850a6e4..d6d07fc23a4 100644 --- a/apps/web/src/locales/ka/messages.json +++ b/apps/web/src/locales/ka/messages.json @@ -2,6 +2,9 @@ "allApplications": { "message": "All applications" }, + "appLogoLabel": { + "message": "Bitwarden logo" + }, "criticalApplications": { "message": "Critical applications" }, @@ -419,18 +422,9 @@ "folder": { "message": "საქაღალდე" }, - "newCustomField": { - "message": "ახალი თქვენზე მორგებული ველი" - }, "value": { "message": "მნიშვნელობა" }, - "dragToSort": { - "message": "გადაადგილე დასახარისხებლად" - }, - "dragToReorder": { - "message": "Drag to reorder" - }, "cfTypeText": { "message": "ტექსტი" }, @@ -4072,8 +4066,8 @@ "updateBrowser": { "message": "Update browser" }, - "generatingRiskInsights": { - "message": "Generating your risk insights..." + "generatingYourRiskInsights": { + "message": "Generating your Risk Insights..." }, "updateBrowserDesc": { "message": "You are using an unsupported web browser. The web vault may not function properly." @@ -6305,6 +6299,21 @@ "sponsoredFamilies": { "message": "Free Bitwarden Families" }, + "sponsoredBitwardenFamilies": { + "message": "Sponsored families" + }, + "noSponsoredFamilies": { + "message": "No sponsored families" + }, + "noSponsoredFamiliesDescription": { + "message": "Sponsored non-member families plans will display here" + }, + "sponsorFreeBitwardenFamilies": { + "message": "Members of your organization are eligible for Free Bitwarden Families. You can sponsor Free Bitwarden Families for employees who are not a member of your Bitwarden organization. Sponsoring a non-member requires an available seat within your organization." + }, + "sponsoredFamiliesRemoveActiveSponsorship": { + "message": "When you remove an active sponsorship, a seat within your organization will be available after the renewal date of the sponsored organization." + }, "sponsoredFamiliesEligible": { "message": "You and your family are eligible for Free Bitwarden Families. Redeem with your personal email to keep your data secure even when you are not at work." }, @@ -6320,6 +6329,18 @@ "sponsoredFamiliesSharedCollections": { "message": "Shared collections for Family secrets" }, + "memberFamilies": { + "message": "Member families" + }, + "noMemberFamilies": { + "message": "No member families" + }, + "noMemberFamiliesDescription": { + "message": "Members who have redeemed family plans will display here" + }, + "membersWithSponsoredFamilies": { + "message": "Members of your organization are eligible for Free Bitwarden Families. Here you can see members who have sponsored a Families organization." + }, "badToken": { "message": "The link is no longer valid. Please have the sponsor resend the offer." }, @@ -7249,6 +7270,9 @@ "followTheStepsBelowToFinishLoggingIn": { "message": "Follow the steps below to finish logging in." }, + "followTheStepsBelowToFinishLoggingInWithSecurityKey": { + "message": "Follow the steps below to finish logging in with your security key." + }, "launchDuo": { "message": "Launch Duo" }, @@ -7983,6 +8007,9 @@ "inviteMember": { "message": "Invite member" }, + "addSponsorship": { + "message": "Add sponsorship" + }, "needsConfirmation": { "message": "Needs confirmation" }, @@ -10571,20 +10598,11 @@ "removeUnlockWithPinPolicyDesc": { "message": "Do not allow members to unlock their account with a PIN." }, - "limitedEventLogs": { - "message": "$PRODUCT_TYPE$ plans do not have access to real event logs", - "placeholders": { - "product_type": { - "content": "$1", - "example": "Teams" - } - } + "upgradeForFullEventsMessage": { + "message": "Event logs are not stored for your organization. Upgrade to a Teams or Enterprise plan to get full access to organization event logs." }, - "upgradeForFullEvents": { - "message": "Get full access to organization event logs by upgrading to a Teams or Enterprise plan." - }, - "upgradeEventLogTitle": { - "message": "Upgrade for real event log data" + "upgradeEventLogTitleMessage": { + "message": "Upgrade to see event logs from your organization." }, "upgradeEventLogMessage": { "message": "These events are examples only and do not reflect real events within your Bitwarden organization." @@ -10600,5 +10618,8 @@ }, "newBusinessUnit": { "message": "New business unit" + }, + "restart": { + "message": "Restart" } } diff --git a/apps/web/src/locales/km/messages.json b/apps/web/src/locales/km/messages.json index 293b3b0486f..6d8d0a9ca42 100644 --- a/apps/web/src/locales/km/messages.json +++ b/apps/web/src/locales/km/messages.json @@ -2,6 +2,9 @@ "allApplications": { "message": "All applications" }, + "appLogoLabel": { + "message": "Bitwarden logo" + }, "criticalApplications": { "message": "Critical applications" }, @@ -419,18 +422,9 @@ "folder": { "message": "Folder" }, - "newCustomField": { - "message": "New custom field" - }, "value": { "message": "Value" }, - "dragToSort": { - "message": "Drag to sort" - }, - "dragToReorder": { - "message": "Drag to reorder" - }, "cfTypeText": { "message": "Text" }, @@ -4072,8 +4066,8 @@ "updateBrowser": { "message": "Update browser" }, - "generatingRiskInsights": { - "message": "Generating your risk insights..." + "generatingYourRiskInsights": { + "message": "Generating your Risk Insights..." }, "updateBrowserDesc": { "message": "You are using an unsupported web browser. The web vault may not function properly." @@ -6305,6 +6299,21 @@ "sponsoredFamilies": { "message": "Free Bitwarden Families" }, + "sponsoredBitwardenFamilies": { + "message": "Sponsored families" + }, + "noSponsoredFamilies": { + "message": "No sponsored families" + }, + "noSponsoredFamiliesDescription": { + "message": "Sponsored non-member families plans will display here" + }, + "sponsorFreeBitwardenFamilies": { + "message": "Members of your organization are eligible for Free Bitwarden Families. You can sponsor Free Bitwarden Families for employees who are not a member of your Bitwarden organization. Sponsoring a non-member requires an available seat within your organization." + }, + "sponsoredFamiliesRemoveActiveSponsorship": { + "message": "When you remove an active sponsorship, a seat within your organization will be available after the renewal date of the sponsored organization." + }, "sponsoredFamiliesEligible": { "message": "You and your family are eligible for Free Bitwarden Families. Redeem with your personal email to keep your data secure even when you are not at work." }, @@ -6320,6 +6329,18 @@ "sponsoredFamiliesSharedCollections": { "message": "Shared collections for Family secrets" }, + "memberFamilies": { + "message": "Member families" + }, + "noMemberFamilies": { + "message": "No member families" + }, + "noMemberFamiliesDescription": { + "message": "Members who have redeemed family plans will display here" + }, + "membersWithSponsoredFamilies": { + "message": "Members of your organization are eligible for Free Bitwarden Families. Here you can see members who have sponsored a Families organization." + }, "badToken": { "message": "The link is no longer valid. Please have the sponsor resend the offer." }, @@ -7249,6 +7270,9 @@ "followTheStepsBelowToFinishLoggingIn": { "message": "Follow the steps below to finish logging in." }, + "followTheStepsBelowToFinishLoggingInWithSecurityKey": { + "message": "Follow the steps below to finish logging in with your security key." + }, "launchDuo": { "message": "Launch Duo" }, @@ -7983,6 +8007,9 @@ "inviteMember": { "message": "Invite member" }, + "addSponsorship": { + "message": "Add sponsorship" + }, "needsConfirmation": { "message": "Needs confirmation" }, @@ -10571,20 +10598,11 @@ "removeUnlockWithPinPolicyDesc": { "message": "Do not allow members to unlock their account with a PIN." }, - "limitedEventLogs": { - "message": "$PRODUCT_TYPE$ plans do not have access to real event logs", - "placeholders": { - "product_type": { - "content": "$1", - "example": "Teams" - } - } + "upgradeForFullEventsMessage": { + "message": "Event logs are not stored for your organization. Upgrade to a Teams or Enterprise plan to get full access to organization event logs." }, - "upgradeForFullEvents": { - "message": "Get full access to organization event logs by upgrading to a Teams or Enterprise plan." - }, - "upgradeEventLogTitle": { - "message": "Upgrade for real event log data" + "upgradeEventLogTitleMessage": { + "message": "Upgrade to see event logs from your organization." }, "upgradeEventLogMessage": { "message": "These events are examples only and do not reflect real events within your Bitwarden organization." @@ -10600,5 +10618,8 @@ }, "newBusinessUnit": { "message": "New business unit" + }, + "restart": { + "message": "Restart" } } diff --git a/apps/web/src/locales/kn/messages.json b/apps/web/src/locales/kn/messages.json index 9074ab87052..d8cc63953ca 100644 --- a/apps/web/src/locales/kn/messages.json +++ b/apps/web/src/locales/kn/messages.json @@ -2,6 +2,9 @@ "allApplications": { "message": "All applications" }, + "appLogoLabel": { + "message": "Bitwarden logo" + }, "criticalApplications": { "message": "Critical applications" }, @@ -419,18 +422,9 @@ "folder": { "message": "ಫೋಲ್ಡರ್" }, - "newCustomField": { - "message": "ಹೊಸ ಕಸ್ಟಮ್ ಕ್ಷೇತ್ರ" - }, "value": { "message": "ಮೌಲ್ಯ" }, - "dragToSort": { - "message": "ವಿಂಗಡಿಸಲು ಎಳೆಯಿರಿ" - }, - "dragToReorder": { - "message": "Drag to reorder" - }, "cfTypeText": { "message": "ಪಠ್ಯ" }, @@ -4072,8 +4066,8 @@ "updateBrowser": { "message": "ಬ್ರೌಸರ್ ನವೀಕರಿಸಿ" }, - "generatingRiskInsights": { - "message": "Generating your risk insights..." + "generatingYourRiskInsights": { + "message": "Generating your Risk Insights..." }, "updateBrowserDesc": { "message": "ನೀವು ಬೆಂಬಲಿಸದ ವೆಬ್ ಬ್ರೌಸರ್ ಅನ್ನು ಬಳಸುತ್ತಿರುವಿರಿ. ವೆಬ್ ವಾಲ್ಟ್ ಸರಿಯಾಗಿ ಕಾರ್ಯನಿರ್ವಹಿಸದೆ ಇರಬಹುದು." @@ -6305,6 +6299,21 @@ "sponsoredFamilies": { "message": "Free Bitwarden Families" }, + "sponsoredBitwardenFamilies": { + "message": "Sponsored families" + }, + "noSponsoredFamilies": { + "message": "No sponsored families" + }, + "noSponsoredFamiliesDescription": { + "message": "Sponsored non-member families plans will display here" + }, + "sponsorFreeBitwardenFamilies": { + "message": "Members of your organization are eligible for Free Bitwarden Families. You can sponsor Free Bitwarden Families for employees who are not a member of your Bitwarden organization. Sponsoring a non-member requires an available seat within your organization." + }, + "sponsoredFamiliesRemoveActiveSponsorship": { + "message": "When you remove an active sponsorship, a seat within your organization will be available after the renewal date of the sponsored organization." + }, "sponsoredFamiliesEligible": { "message": "You and your family are eligible for Free Bitwarden Families. Redeem with your personal email to keep your data secure even when you are not at work." }, @@ -6320,6 +6329,18 @@ "sponsoredFamiliesSharedCollections": { "message": "Shared collections for Family secrets" }, + "memberFamilies": { + "message": "Member families" + }, + "noMemberFamilies": { + "message": "No member families" + }, + "noMemberFamiliesDescription": { + "message": "Members who have redeemed family plans will display here" + }, + "membersWithSponsoredFamilies": { + "message": "Members of your organization are eligible for Free Bitwarden Families. Here you can see members who have sponsored a Families organization." + }, "badToken": { "message": "The link is no longer valid. Please have the sponsor resend the offer." }, @@ -7249,6 +7270,9 @@ "followTheStepsBelowToFinishLoggingIn": { "message": "Follow the steps below to finish logging in." }, + "followTheStepsBelowToFinishLoggingInWithSecurityKey": { + "message": "Follow the steps below to finish logging in with your security key." + }, "launchDuo": { "message": "Launch Duo" }, @@ -7983,6 +8007,9 @@ "inviteMember": { "message": "Invite member" }, + "addSponsorship": { + "message": "Add sponsorship" + }, "needsConfirmation": { "message": "Needs confirmation" }, @@ -10571,20 +10598,11 @@ "removeUnlockWithPinPolicyDesc": { "message": "Do not allow members to unlock their account with a PIN." }, - "limitedEventLogs": { - "message": "$PRODUCT_TYPE$ plans do not have access to real event logs", - "placeholders": { - "product_type": { - "content": "$1", - "example": "Teams" - } - } + "upgradeForFullEventsMessage": { + "message": "Event logs are not stored for your organization. Upgrade to a Teams or Enterprise plan to get full access to organization event logs." }, - "upgradeForFullEvents": { - "message": "Get full access to organization event logs by upgrading to a Teams or Enterprise plan." - }, - "upgradeEventLogTitle": { - "message": "Upgrade for real event log data" + "upgradeEventLogTitleMessage": { + "message": "Upgrade to see event logs from your organization." }, "upgradeEventLogMessage": { "message": "These events are examples only and do not reflect real events within your Bitwarden organization." @@ -10600,5 +10618,8 @@ }, "newBusinessUnit": { "message": "New business unit" + }, + "restart": { + "message": "Restart" } } diff --git a/apps/web/src/locales/ko/messages.json b/apps/web/src/locales/ko/messages.json index b0868c3e601..cc391529d7c 100644 --- a/apps/web/src/locales/ko/messages.json +++ b/apps/web/src/locales/ko/messages.json @@ -2,6 +2,9 @@ "allApplications": { "message": "All applications" }, + "appLogoLabel": { + "message": "Bitwarden logo" + }, "criticalApplications": { "message": "Critical applications" }, @@ -419,18 +422,9 @@ "folder": { "message": "폴더" }, - "newCustomField": { - "message": "새 사용자 지정 필드" - }, "value": { "message": "값" }, - "dragToSort": { - "message": "드래그하여 정렬" - }, - "dragToReorder": { - "message": "Drag to reorder" - }, "cfTypeText": { "message": "텍스트" }, @@ -4072,8 +4066,8 @@ "updateBrowser": { "message": "브라우저 업데이트" }, - "generatingRiskInsights": { - "message": "Generating your risk insights..." + "generatingYourRiskInsights": { + "message": "Generating your Risk Insights..." }, "updateBrowserDesc": { "message": "지원하지 않는 웹 브라우저를 사용하고 있습니다. 웹 보관함 기능이 제대로 동작하지 않을 수 있습니다." @@ -6305,6 +6299,21 @@ "sponsoredFamilies": { "message": "Free Bitwarden Families" }, + "sponsoredBitwardenFamilies": { + "message": "Sponsored families" + }, + "noSponsoredFamilies": { + "message": "No sponsored families" + }, + "noSponsoredFamiliesDescription": { + "message": "Sponsored non-member families plans will display here" + }, + "sponsorFreeBitwardenFamilies": { + "message": "Members of your organization are eligible for Free Bitwarden Families. You can sponsor Free Bitwarden Families for employees who are not a member of your Bitwarden organization. Sponsoring a non-member requires an available seat within your organization." + }, + "sponsoredFamiliesRemoveActiveSponsorship": { + "message": "When you remove an active sponsorship, a seat within your organization will be available after the renewal date of the sponsored organization." + }, "sponsoredFamiliesEligible": { "message": "You and your family are eligible for Free Bitwarden Families. Redeem with your personal email to keep your data secure even when you are not at work." }, @@ -6320,6 +6329,18 @@ "sponsoredFamiliesSharedCollections": { "message": "Shared collections for Family secrets" }, + "memberFamilies": { + "message": "Member families" + }, + "noMemberFamilies": { + "message": "No member families" + }, + "noMemberFamiliesDescription": { + "message": "Members who have redeemed family plans will display here" + }, + "membersWithSponsoredFamilies": { + "message": "Members of your organization are eligible for Free Bitwarden Families. Here you can see members who have sponsored a Families organization." + }, "badToken": { "message": "The link is no longer valid. Please have the sponsor resend the offer." }, @@ -7249,6 +7270,9 @@ "followTheStepsBelowToFinishLoggingIn": { "message": "Follow the steps below to finish logging in." }, + "followTheStepsBelowToFinishLoggingInWithSecurityKey": { + "message": "Follow the steps below to finish logging in with your security key." + }, "launchDuo": { "message": "Launch Duo" }, @@ -7983,6 +8007,9 @@ "inviteMember": { "message": "구성원 초대" }, + "addSponsorship": { + "message": "Add sponsorship" + }, "needsConfirmation": { "message": "Needs confirmation" }, @@ -10571,20 +10598,11 @@ "removeUnlockWithPinPolicyDesc": { "message": "Do not allow members to unlock their account with a PIN." }, - "limitedEventLogs": { - "message": "$PRODUCT_TYPE$ plans do not have access to real event logs", - "placeholders": { - "product_type": { - "content": "$1", - "example": "Teams" - } - } + "upgradeForFullEventsMessage": { + "message": "Event logs are not stored for your organization. Upgrade to a Teams or Enterprise plan to get full access to organization event logs." }, - "upgradeForFullEvents": { - "message": "Get full access to organization event logs by upgrading to a Teams or Enterprise plan." - }, - "upgradeEventLogTitle": { - "message": "Upgrade for real event log data" + "upgradeEventLogTitleMessage": { + "message": "Upgrade to see event logs from your organization." }, "upgradeEventLogMessage": { "message": "These events are examples only and do not reflect real events within your Bitwarden organization." @@ -10600,5 +10618,8 @@ }, "newBusinessUnit": { "message": "New business unit" + }, + "restart": { + "message": "Restart" } } diff --git a/apps/web/src/locales/lv/messages.json b/apps/web/src/locales/lv/messages.json index 3faf2a549de..4fa7013ca4f 100644 --- a/apps/web/src/locales/lv/messages.json +++ b/apps/web/src/locales/lv/messages.json @@ -2,6 +2,9 @@ "allApplications": { "message": "Visas lietotnes" }, + "appLogoLabel": { + "message": "Bitwarden logotips" + }, "criticalApplications": { "message": "Kritiskās lietotnes" }, @@ -419,18 +422,9 @@ "folder": { "message": "Mape" }, - "newCustomField": { - "message": "Jauns pielāgotais lauks" - }, "value": { "message": "Vērtība" }, - "dragToSort": { - "message": "Vilkt, lai kārtotu" - }, - "dragToReorder": { - "message": "Vilkt, lai pārkārtotu" - }, "cfTypeText": { "message": "Teksts" }, @@ -2105,13 +2099,13 @@ "message": "Pielāgot" }, "newCustomDomain": { - "message": "Jauns pielāgotais domēns" + "message": "Jauns pielāgots domēns" }, "newCustomDomainDesc": { "message": "Jāievada ar komatiem atdalīts domēnu saraksts. Ir atļauti tikai \"pamata\" domēni. Neievadīt apakšdomēnus. Piemēram, ievadīt \"google.com\" \"www.google.com\" vietā. Ir iespējams ievadīt arī \"androidapp://package.name\", lai saistītu Android lietotni ar citiem tīmekļvietņu domēniem." }, "customDomainX": { - "message": "Pielāgotais domēns $INDEX$", + "message": "Pielāgots domēns $INDEX$", "placeholders": { "index": { "content": "$1", @@ -2812,7 +2806,7 @@ "message": "Izvēlētais maksājumu veids tiks izmantots jebkuru neapmaksātu abonementu apmaksai." }, "paymentChargedWithTrial": { - "message": "Pašreizējā plānā ir iekļauts bezmaksas 7 dienu izmēģinājuma laiks. Izvēlētais apmaksas veids netiks izmantots līdz izmēģinājuma beigā. Norēķini notiks katru $INTERVAL$. To var atcelt jebkurā brīdī." + "message": "Pašreizējā plānā ir iekļauts bezmaksas 7 dienu izmēģinājuma laiks. Izvēlētais apmaksas veids netiks izmantots līdz izmēģinājuma beigām. To var atcelt jebkurā brīdī." }, "paymentInformation": { "message": "Maksājuma informācija" @@ -2842,7 +2836,7 @@ "message": "Tiek gaidīta atcelšana" }, "subscriptionPendingCanceled": { - "message": "Abonements ir atzīmēts atcelšanai pašreizējā norēķinu perioda beigās." + "message": "Abonements ir atzīmēts atcelšanai pašreizējā norēķinu laika posma beigās." }, "reinstateSubscription": { "message": "Atjaunot abonementu" @@ -4072,7 +4066,7 @@ "updateBrowser": { "message": "Atjaunināt pārlūku" }, - "generatingRiskInsights": { + "generatingYourRiskInsights": { "message": "Tiek veidots ieskats par riskiem..." }, "updateBrowserDesc": { @@ -6305,6 +6299,21 @@ "sponsoredFamilies": { "message": "Bezmaksas Bitwarden ģimenēm" }, + "sponsoredBitwardenFamilies": { + "message": "Sponsored families" + }, + "noSponsoredFamilies": { + "message": "No sponsored families" + }, + "noSponsoredFamiliesDescription": { + "message": "Sponsored non-member families plans will display here" + }, + "sponsorFreeBitwardenFamilies": { + "message": "Members of your organization are eligible for Free Bitwarden Families. You can sponsor Free Bitwarden Families for employees who are not a member of your Bitwarden organization. Sponsoring a non-member requires an available seat within your organization." + }, + "sponsoredFamiliesRemoveActiveSponsorship": { + "message": "When you remove an active sponsorship, a seat within your organization will be available after the renewal date of the sponsored organization." + }, "sponsoredFamiliesEligible": { "message": "Tu un Tava ģimene esat atbilstīgi bezmaksas Bitwarden Families. Piesakies ar personīgo e-pasta adresi, lai turētu datus drošībā pat tad, kad neesi darbā!" }, @@ -6320,6 +6329,18 @@ "sponsoredFamiliesSharedCollections": { "message": "Ģimenes noslēpumu kopīgotie krājumi" }, + "memberFamilies": { + "message": "Member families" + }, + "noMemberFamilies": { + "message": "No member families" + }, + "noMemberFamiliesDescription": { + "message": "Members who have redeemed family plans will display here" + }, + "membersWithSponsoredFamilies": { + "message": "Members of your organization are eligible for Free Bitwarden Families. Here you can see members who have sponsored a Families organization." + }, "badToken": { "message": "Saite vairs nav derīga. Lūdz pabalstītāju atkārtoti nosūtīt piedāvājumu!" }, @@ -6594,7 +6615,7 @@ "message": "Papildu konfigurācijā ir iespējams norādīt URL katram pakalpojumam atsevišķi." }, "selfHostedEnvFormInvalid": { - "message": "Jāpievieno vai no servera pamata URL vai vismaz viena pielāgota vide." + "message": "Jāpievieno vai nu servera pamata URL vai vismaz viena pielāgota vide." }, "apiUrl": { "message": "API servera URL" @@ -7249,6 +7270,9 @@ "followTheStepsBelowToFinishLoggingIn": { "message": "Jāseko zemāk esošajām norādēm, lai pabeigtu pieteikšanos." }, + "followTheStepsBelowToFinishLoggingInWithSecurityKey": { + "message": "Jāizpilda zemāk esošās darbības, lai pabeigtu pieteikšanos ar savu drošības atslēgu." + }, "launchDuo": { "message": "Palaist DUO" }, @@ -7983,6 +8007,9 @@ "inviteMember": { "message": "Uzaicināt dalībnieku" }, + "addSponsorship": { + "message": "Add sponsorship" + }, "needsConfirmation": { "message": "Nepieciešams apstiprinājums" }, @@ -10571,20 +10598,11 @@ "removeUnlockWithPinPolicyDesc": { "message": "Neļaut dalībniekiem atslēgt savu kontu ar PIN." }, - "limitedEventLogs": { - "message": "$PRODUCT_TYPE$ plāniem nav piekļuve īstajiem notikumu žurnāliem", - "placeholders": { - "product_type": { - "content": "$1", - "example": "Teams" - } - } + "upgradeForFullEventsMessage": { + "message": "Notikumu žurnāli Tavai apvienībai netiek glabāti. Jāuzlabo uz komandu vai uzņēmējdarbības plānu, lai iegūtu pilnu piekļuvi apvienības notikumu žurnāliem." }, - "upgradeForFullEvents": { - "message": "Pilna piekļuve apvienības notikumu žurnāliem ir iegūstama, ja izmanto Komandu vai Uzņēmējdarbības plānu." - }, - "upgradeEventLogTitle": { - "message": "Uzlabot, lai piekļūtu īstajiem notikumu žurnāla datiem" + "upgradeEventLogTitleMessage": { + "message": "Jāuzlabo, lai redzētu savas apvienības notikumu žurnālus." }, "upgradeEventLogMessage": { "message": "Šie notikumu ir tikai piemēri, un tie neatspoguļo īstus notikumus Bitwarden apvienībā." @@ -10600,5 +10618,8 @@ }, "newBusinessUnit": { "message": "Jauna uzņēmējdarbības vienība" + }, + "restart": { + "message": "Palaist no jauna" } } diff --git a/apps/web/src/locales/ml/messages.json b/apps/web/src/locales/ml/messages.json index 620d1285e65..90d5e5c8015 100644 --- a/apps/web/src/locales/ml/messages.json +++ b/apps/web/src/locales/ml/messages.json @@ -2,6 +2,9 @@ "allApplications": { "message": "All applications" }, + "appLogoLabel": { + "message": "Bitwarden logo" + }, "criticalApplications": { "message": "Critical applications" }, @@ -419,18 +422,9 @@ "folder": { "message": "ഫോൾഡർ" }, - "newCustomField": { - "message": "പുതിയ ഇഷ്ടാനുസൃത ഫീൽഡ്" - }, "value": { "message": "മൂല്യം" }, - "dragToSort": { - "message": "അടുക്കാൻ വലിച്ചിടുക" - }, - "dragToReorder": { - "message": "Drag to reorder" - }, "cfTypeText": { "message": "വാചകം" }, @@ -4072,8 +4066,8 @@ "updateBrowser": { "message": "ബ്രൌസർ അപ്ഡേറ്റുചെയ്യുക" }, - "generatingRiskInsights": { - "message": "Generating your risk insights..." + "generatingYourRiskInsights": { + "message": "Generating your Risk Insights..." }, "updateBrowserDesc": { "message": "You are using an unsupported web browser. The web vault may not function properly." @@ -6305,6 +6299,21 @@ "sponsoredFamilies": { "message": "Free Bitwarden Families" }, + "sponsoredBitwardenFamilies": { + "message": "Sponsored families" + }, + "noSponsoredFamilies": { + "message": "No sponsored families" + }, + "noSponsoredFamiliesDescription": { + "message": "Sponsored non-member families plans will display here" + }, + "sponsorFreeBitwardenFamilies": { + "message": "Members of your organization are eligible for Free Bitwarden Families. You can sponsor Free Bitwarden Families for employees who are not a member of your Bitwarden organization. Sponsoring a non-member requires an available seat within your organization." + }, + "sponsoredFamiliesRemoveActiveSponsorship": { + "message": "When you remove an active sponsorship, a seat within your organization will be available after the renewal date of the sponsored organization." + }, "sponsoredFamiliesEligible": { "message": "You and your family are eligible for Free Bitwarden Families. Redeem with your personal email to keep your data secure even when you are not at work." }, @@ -6320,6 +6329,18 @@ "sponsoredFamiliesSharedCollections": { "message": "Shared collections for Family secrets" }, + "memberFamilies": { + "message": "Member families" + }, + "noMemberFamilies": { + "message": "No member families" + }, + "noMemberFamiliesDescription": { + "message": "Members who have redeemed family plans will display here" + }, + "membersWithSponsoredFamilies": { + "message": "Members of your organization are eligible for Free Bitwarden Families. Here you can see members who have sponsored a Families organization." + }, "badToken": { "message": "The link is no longer valid. Please have the sponsor resend the offer." }, @@ -7249,6 +7270,9 @@ "followTheStepsBelowToFinishLoggingIn": { "message": "Follow the steps below to finish logging in." }, + "followTheStepsBelowToFinishLoggingInWithSecurityKey": { + "message": "Follow the steps below to finish logging in with your security key." + }, "launchDuo": { "message": "Launch Duo" }, @@ -7983,6 +8007,9 @@ "inviteMember": { "message": "Invite member" }, + "addSponsorship": { + "message": "Add sponsorship" + }, "needsConfirmation": { "message": "Needs confirmation" }, @@ -10571,20 +10598,11 @@ "removeUnlockWithPinPolicyDesc": { "message": "Do not allow members to unlock their account with a PIN." }, - "limitedEventLogs": { - "message": "$PRODUCT_TYPE$ plans do not have access to real event logs", - "placeholders": { - "product_type": { - "content": "$1", - "example": "Teams" - } - } + "upgradeForFullEventsMessage": { + "message": "Event logs are not stored for your organization. Upgrade to a Teams or Enterprise plan to get full access to organization event logs." }, - "upgradeForFullEvents": { - "message": "Get full access to organization event logs by upgrading to a Teams or Enterprise plan." - }, - "upgradeEventLogTitle": { - "message": "Upgrade for real event log data" + "upgradeEventLogTitleMessage": { + "message": "Upgrade to see event logs from your organization." }, "upgradeEventLogMessage": { "message": "These events are examples only and do not reflect real events within your Bitwarden organization." @@ -10600,5 +10618,8 @@ }, "newBusinessUnit": { "message": "New business unit" + }, + "restart": { + "message": "Restart" } } diff --git a/apps/web/src/locales/mr/messages.json b/apps/web/src/locales/mr/messages.json index 293b3b0486f..6d8d0a9ca42 100644 --- a/apps/web/src/locales/mr/messages.json +++ b/apps/web/src/locales/mr/messages.json @@ -2,6 +2,9 @@ "allApplications": { "message": "All applications" }, + "appLogoLabel": { + "message": "Bitwarden logo" + }, "criticalApplications": { "message": "Critical applications" }, @@ -419,18 +422,9 @@ "folder": { "message": "Folder" }, - "newCustomField": { - "message": "New custom field" - }, "value": { "message": "Value" }, - "dragToSort": { - "message": "Drag to sort" - }, - "dragToReorder": { - "message": "Drag to reorder" - }, "cfTypeText": { "message": "Text" }, @@ -4072,8 +4066,8 @@ "updateBrowser": { "message": "Update browser" }, - "generatingRiskInsights": { - "message": "Generating your risk insights..." + "generatingYourRiskInsights": { + "message": "Generating your Risk Insights..." }, "updateBrowserDesc": { "message": "You are using an unsupported web browser. The web vault may not function properly." @@ -6305,6 +6299,21 @@ "sponsoredFamilies": { "message": "Free Bitwarden Families" }, + "sponsoredBitwardenFamilies": { + "message": "Sponsored families" + }, + "noSponsoredFamilies": { + "message": "No sponsored families" + }, + "noSponsoredFamiliesDescription": { + "message": "Sponsored non-member families plans will display here" + }, + "sponsorFreeBitwardenFamilies": { + "message": "Members of your organization are eligible for Free Bitwarden Families. You can sponsor Free Bitwarden Families for employees who are not a member of your Bitwarden organization. Sponsoring a non-member requires an available seat within your organization." + }, + "sponsoredFamiliesRemoveActiveSponsorship": { + "message": "When you remove an active sponsorship, a seat within your organization will be available after the renewal date of the sponsored organization." + }, "sponsoredFamiliesEligible": { "message": "You and your family are eligible for Free Bitwarden Families. Redeem with your personal email to keep your data secure even when you are not at work." }, @@ -6320,6 +6329,18 @@ "sponsoredFamiliesSharedCollections": { "message": "Shared collections for Family secrets" }, + "memberFamilies": { + "message": "Member families" + }, + "noMemberFamilies": { + "message": "No member families" + }, + "noMemberFamiliesDescription": { + "message": "Members who have redeemed family plans will display here" + }, + "membersWithSponsoredFamilies": { + "message": "Members of your organization are eligible for Free Bitwarden Families. Here you can see members who have sponsored a Families organization." + }, "badToken": { "message": "The link is no longer valid. Please have the sponsor resend the offer." }, @@ -7249,6 +7270,9 @@ "followTheStepsBelowToFinishLoggingIn": { "message": "Follow the steps below to finish logging in." }, + "followTheStepsBelowToFinishLoggingInWithSecurityKey": { + "message": "Follow the steps below to finish logging in with your security key." + }, "launchDuo": { "message": "Launch Duo" }, @@ -7983,6 +8007,9 @@ "inviteMember": { "message": "Invite member" }, + "addSponsorship": { + "message": "Add sponsorship" + }, "needsConfirmation": { "message": "Needs confirmation" }, @@ -10571,20 +10598,11 @@ "removeUnlockWithPinPolicyDesc": { "message": "Do not allow members to unlock their account with a PIN." }, - "limitedEventLogs": { - "message": "$PRODUCT_TYPE$ plans do not have access to real event logs", - "placeholders": { - "product_type": { - "content": "$1", - "example": "Teams" - } - } + "upgradeForFullEventsMessage": { + "message": "Event logs are not stored for your organization. Upgrade to a Teams or Enterprise plan to get full access to organization event logs." }, - "upgradeForFullEvents": { - "message": "Get full access to organization event logs by upgrading to a Teams or Enterprise plan." - }, - "upgradeEventLogTitle": { - "message": "Upgrade for real event log data" + "upgradeEventLogTitleMessage": { + "message": "Upgrade to see event logs from your organization." }, "upgradeEventLogMessage": { "message": "These events are examples only and do not reflect real events within your Bitwarden organization." @@ -10600,5 +10618,8 @@ }, "newBusinessUnit": { "message": "New business unit" + }, + "restart": { + "message": "Restart" } } diff --git a/apps/web/src/locales/my/messages.json b/apps/web/src/locales/my/messages.json index 293b3b0486f..6d8d0a9ca42 100644 --- a/apps/web/src/locales/my/messages.json +++ b/apps/web/src/locales/my/messages.json @@ -2,6 +2,9 @@ "allApplications": { "message": "All applications" }, + "appLogoLabel": { + "message": "Bitwarden logo" + }, "criticalApplications": { "message": "Critical applications" }, @@ -419,18 +422,9 @@ "folder": { "message": "Folder" }, - "newCustomField": { - "message": "New custom field" - }, "value": { "message": "Value" }, - "dragToSort": { - "message": "Drag to sort" - }, - "dragToReorder": { - "message": "Drag to reorder" - }, "cfTypeText": { "message": "Text" }, @@ -4072,8 +4066,8 @@ "updateBrowser": { "message": "Update browser" }, - "generatingRiskInsights": { - "message": "Generating your risk insights..." + "generatingYourRiskInsights": { + "message": "Generating your Risk Insights..." }, "updateBrowserDesc": { "message": "You are using an unsupported web browser. The web vault may not function properly." @@ -6305,6 +6299,21 @@ "sponsoredFamilies": { "message": "Free Bitwarden Families" }, + "sponsoredBitwardenFamilies": { + "message": "Sponsored families" + }, + "noSponsoredFamilies": { + "message": "No sponsored families" + }, + "noSponsoredFamiliesDescription": { + "message": "Sponsored non-member families plans will display here" + }, + "sponsorFreeBitwardenFamilies": { + "message": "Members of your organization are eligible for Free Bitwarden Families. You can sponsor Free Bitwarden Families for employees who are not a member of your Bitwarden organization. Sponsoring a non-member requires an available seat within your organization." + }, + "sponsoredFamiliesRemoveActiveSponsorship": { + "message": "When you remove an active sponsorship, a seat within your organization will be available after the renewal date of the sponsored organization." + }, "sponsoredFamiliesEligible": { "message": "You and your family are eligible for Free Bitwarden Families. Redeem with your personal email to keep your data secure even when you are not at work." }, @@ -6320,6 +6329,18 @@ "sponsoredFamiliesSharedCollections": { "message": "Shared collections for Family secrets" }, + "memberFamilies": { + "message": "Member families" + }, + "noMemberFamilies": { + "message": "No member families" + }, + "noMemberFamiliesDescription": { + "message": "Members who have redeemed family plans will display here" + }, + "membersWithSponsoredFamilies": { + "message": "Members of your organization are eligible for Free Bitwarden Families. Here you can see members who have sponsored a Families organization." + }, "badToken": { "message": "The link is no longer valid. Please have the sponsor resend the offer." }, @@ -7249,6 +7270,9 @@ "followTheStepsBelowToFinishLoggingIn": { "message": "Follow the steps below to finish logging in." }, + "followTheStepsBelowToFinishLoggingInWithSecurityKey": { + "message": "Follow the steps below to finish logging in with your security key." + }, "launchDuo": { "message": "Launch Duo" }, @@ -7983,6 +8007,9 @@ "inviteMember": { "message": "Invite member" }, + "addSponsorship": { + "message": "Add sponsorship" + }, "needsConfirmation": { "message": "Needs confirmation" }, @@ -10571,20 +10598,11 @@ "removeUnlockWithPinPolicyDesc": { "message": "Do not allow members to unlock their account with a PIN." }, - "limitedEventLogs": { - "message": "$PRODUCT_TYPE$ plans do not have access to real event logs", - "placeholders": { - "product_type": { - "content": "$1", - "example": "Teams" - } - } + "upgradeForFullEventsMessage": { + "message": "Event logs are not stored for your organization. Upgrade to a Teams or Enterprise plan to get full access to organization event logs." }, - "upgradeForFullEvents": { - "message": "Get full access to organization event logs by upgrading to a Teams or Enterprise plan." - }, - "upgradeEventLogTitle": { - "message": "Upgrade for real event log data" + "upgradeEventLogTitleMessage": { + "message": "Upgrade to see event logs from your organization." }, "upgradeEventLogMessage": { "message": "These events are examples only and do not reflect real events within your Bitwarden organization." @@ -10600,5 +10618,8 @@ }, "newBusinessUnit": { "message": "New business unit" + }, + "restart": { + "message": "Restart" } } diff --git a/apps/web/src/locales/nb/messages.json b/apps/web/src/locales/nb/messages.json index c31140eb136..0f2221846a7 100644 --- a/apps/web/src/locales/nb/messages.json +++ b/apps/web/src/locales/nb/messages.json @@ -2,6 +2,9 @@ "allApplications": { "message": "All applications" }, + "appLogoLabel": { + "message": "Bitwarden logo" + }, "criticalApplications": { "message": "Critical applications" }, @@ -419,18 +422,9 @@ "folder": { "message": "Mappe" }, - "newCustomField": { - "message": "Nytt egendefinert felt" - }, "value": { "message": "Verdi" }, - "dragToSort": { - "message": "Dra for å sortere" - }, - "dragToReorder": { - "message": "Drag to reorder" - }, "cfTypeText": { "message": "Tekst" }, @@ -4072,8 +4066,8 @@ "updateBrowser": { "message": "Oppdater nettleseren" }, - "generatingRiskInsights": { - "message": "Generating your risk insights..." + "generatingYourRiskInsights": { + "message": "Generating your Risk Insights..." }, "updateBrowserDesc": { "message": "Du bruker en ustøttet nettleser. Netthvelvet vil kanskje ikke fungere ordentlig." @@ -6305,6 +6299,21 @@ "sponsoredFamilies": { "message": "Gratis Bitwarden Familier" }, + "sponsoredBitwardenFamilies": { + "message": "Sponsored families" + }, + "noSponsoredFamilies": { + "message": "No sponsored families" + }, + "noSponsoredFamiliesDescription": { + "message": "Sponsored non-member families plans will display here" + }, + "sponsorFreeBitwardenFamilies": { + "message": "Members of your organization are eligible for Free Bitwarden Families. You can sponsor Free Bitwarden Families for employees who are not a member of your Bitwarden organization. Sponsoring a non-member requires an available seat within your organization." + }, + "sponsoredFamiliesRemoveActiveSponsorship": { + "message": "When you remove an active sponsorship, a seat within your organization will be available after the renewal date of the sponsored organization." + }, "sponsoredFamiliesEligible": { "message": "Du og din familie er kvalifisert for Free Bitwarden Familier. Løs inn med din personlige e-post for å holde dataene dine sikre, selv om du ikke er på jobb." }, @@ -6320,6 +6329,18 @@ "sponsoredFamiliesSharedCollections": { "message": "Delte samlinger til familie-hemmeligheter" }, + "memberFamilies": { + "message": "Member families" + }, + "noMemberFamilies": { + "message": "No member families" + }, + "noMemberFamiliesDescription": { + "message": "Members who have redeemed family plans will display here" + }, + "membersWithSponsoredFamilies": { + "message": "Members of your organization are eligible for Free Bitwarden Families. Here you can see members who have sponsored a Families organization." + }, "badToken": { "message": "Lenken er ikke lenger gyldig. Vennligst ha sponsor på nytt tilbudet." }, @@ -7249,6 +7270,9 @@ "followTheStepsBelowToFinishLoggingIn": { "message": "Follow the steps below to finish logging in." }, + "followTheStepsBelowToFinishLoggingInWithSecurityKey": { + "message": "Follow the steps below to finish logging in with your security key." + }, "launchDuo": { "message": "Start Duo" }, @@ -7983,6 +8007,9 @@ "inviteMember": { "message": "Inviter medlem" }, + "addSponsorship": { + "message": "Add sponsorship" + }, "needsConfirmation": { "message": "Behøver bekreftelse" }, @@ -10571,20 +10598,11 @@ "removeUnlockWithPinPolicyDesc": { "message": "Do not allow members to unlock their account with a PIN." }, - "limitedEventLogs": { - "message": "$PRODUCT_TYPE$ plans do not have access to real event logs", - "placeholders": { - "product_type": { - "content": "$1", - "example": "Teams" - } - } + "upgradeForFullEventsMessage": { + "message": "Event logs are not stored for your organization. Upgrade to a Teams or Enterprise plan to get full access to organization event logs." }, - "upgradeForFullEvents": { - "message": "Get full access to organization event logs by upgrading to a Teams or Enterprise plan." - }, - "upgradeEventLogTitle": { - "message": "Upgrade for real event log data" + "upgradeEventLogTitleMessage": { + "message": "Upgrade to see event logs from your organization." }, "upgradeEventLogMessage": { "message": "These events are examples only and do not reflect real events within your Bitwarden organization." @@ -10600,5 +10618,8 @@ }, "newBusinessUnit": { "message": "New business unit" + }, + "restart": { + "message": "Restart" } } diff --git a/apps/web/src/locales/ne/messages.json b/apps/web/src/locales/ne/messages.json index 6052556f397..41f1e49fe10 100644 --- a/apps/web/src/locales/ne/messages.json +++ b/apps/web/src/locales/ne/messages.json @@ -2,6 +2,9 @@ "allApplications": { "message": "All applications" }, + "appLogoLabel": { + "message": "Bitwarden logo" + }, "criticalApplications": { "message": "Critical applications" }, @@ -419,18 +422,9 @@ "folder": { "message": "फोल्डर" }, - "newCustomField": { - "message": "New custom field" - }, "value": { "message": "Value" }, - "dragToSort": { - "message": "क्रमबद्ध गर्न तान्नुहोस्" - }, - "dragToReorder": { - "message": "Drag to reorder" - }, "cfTypeText": { "message": "पाठ" }, @@ -4072,8 +4066,8 @@ "updateBrowser": { "message": "Update browser" }, - "generatingRiskInsights": { - "message": "Generating your risk insights..." + "generatingYourRiskInsights": { + "message": "Generating your Risk Insights..." }, "updateBrowserDesc": { "message": "You are using an unsupported web browser. The web vault may not function properly." @@ -6305,6 +6299,21 @@ "sponsoredFamilies": { "message": "Free Bitwarden Families" }, + "sponsoredBitwardenFamilies": { + "message": "Sponsored families" + }, + "noSponsoredFamilies": { + "message": "No sponsored families" + }, + "noSponsoredFamiliesDescription": { + "message": "Sponsored non-member families plans will display here" + }, + "sponsorFreeBitwardenFamilies": { + "message": "Members of your organization are eligible for Free Bitwarden Families. You can sponsor Free Bitwarden Families for employees who are not a member of your Bitwarden organization. Sponsoring a non-member requires an available seat within your organization." + }, + "sponsoredFamiliesRemoveActiveSponsorship": { + "message": "When you remove an active sponsorship, a seat within your organization will be available after the renewal date of the sponsored organization." + }, "sponsoredFamiliesEligible": { "message": "You and your family are eligible for Free Bitwarden Families. Redeem with your personal email to keep your data secure even when you are not at work." }, @@ -6320,6 +6329,18 @@ "sponsoredFamiliesSharedCollections": { "message": "Shared collections for Family secrets" }, + "memberFamilies": { + "message": "Member families" + }, + "noMemberFamilies": { + "message": "No member families" + }, + "noMemberFamiliesDescription": { + "message": "Members who have redeemed family plans will display here" + }, + "membersWithSponsoredFamilies": { + "message": "Members of your organization are eligible for Free Bitwarden Families. Here you can see members who have sponsored a Families organization." + }, "badToken": { "message": "The link is no longer valid. Please have the sponsor resend the offer." }, @@ -7249,6 +7270,9 @@ "followTheStepsBelowToFinishLoggingIn": { "message": "Follow the steps below to finish logging in." }, + "followTheStepsBelowToFinishLoggingInWithSecurityKey": { + "message": "Follow the steps below to finish logging in with your security key." + }, "launchDuo": { "message": "Launch Duo" }, @@ -7983,6 +8007,9 @@ "inviteMember": { "message": "Invite member" }, + "addSponsorship": { + "message": "Add sponsorship" + }, "needsConfirmation": { "message": "Needs confirmation" }, @@ -10571,20 +10598,11 @@ "removeUnlockWithPinPolicyDesc": { "message": "Do not allow members to unlock their account with a PIN." }, - "limitedEventLogs": { - "message": "$PRODUCT_TYPE$ plans do not have access to real event logs", - "placeholders": { - "product_type": { - "content": "$1", - "example": "Teams" - } - } + "upgradeForFullEventsMessage": { + "message": "Event logs are not stored for your organization. Upgrade to a Teams or Enterprise plan to get full access to organization event logs." }, - "upgradeForFullEvents": { - "message": "Get full access to organization event logs by upgrading to a Teams or Enterprise plan." - }, - "upgradeEventLogTitle": { - "message": "Upgrade for real event log data" + "upgradeEventLogTitleMessage": { + "message": "Upgrade to see event logs from your organization." }, "upgradeEventLogMessage": { "message": "These events are examples only and do not reflect real events within your Bitwarden organization." @@ -10600,5 +10618,8 @@ }, "newBusinessUnit": { "message": "New business unit" + }, + "restart": { + "message": "Restart" } } diff --git a/apps/web/src/locales/nl/messages.json b/apps/web/src/locales/nl/messages.json index b6811d9289f..495a594e2a8 100644 --- a/apps/web/src/locales/nl/messages.json +++ b/apps/web/src/locales/nl/messages.json @@ -2,6 +2,9 @@ "allApplications": { "message": "Alle applicaties" }, + "appLogoLabel": { + "message": "Bitwarden-logo" + }, "criticalApplications": { "message": "Belangrijke applicaties" }, @@ -419,18 +422,9 @@ "folder": { "message": "Map" }, - "newCustomField": { - "message": "Nieuw extra veld" - }, "value": { "message": "Waarde" }, - "dragToSort": { - "message": "Slepen om te sorteren" - }, - "dragToReorder": { - "message": "Sleep om te herschikken" - }, "cfTypeText": { "message": "Tekst" }, @@ -4072,7 +4066,7 @@ "updateBrowser": { "message": "Webbrowser bijwerken" }, - "generatingRiskInsights": { + "generatingYourRiskInsights": { "message": "Je risico-inzichten genereren..." }, "updateBrowserDesc": { @@ -6305,6 +6299,21 @@ "sponsoredFamilies": { "message": "Gratis Bitwarden Families" }, + "sponsoredBitwardenFamilies": { + "message": "Gesponsorde families" + }, + "noSponsoredFamilies": { + "message": "Geen gesponsorde families" + }, + "noSponsoredFamiliesDescription": { + "message": "Gesponsorde niet-lid Familie-plannen worden hier weergegeven" + }, + "sponsorFreeBitwardenFamilies": { + "message": "Leden van je organisatie komen in aanmerking voor gratis Bitwarden Familie. Je kunt de gratis Bitwarden Familie sponsoren voor medewerkers die geen lid zijn van je Bitwarden-organisatie. Het sponsoren van een niet-lid vereist een beschikbare zitplaats binnen je organisatie." + }, + "sponsoredFamiliesRemoveActiveSponsorship": { + "message": "Als je een actieve sponsoring verwijdert, komt er een plaats binnen je organisatie beschikbaar na de verlengingsdatum van de gesponsorde organisatie." + }, "sponsoredFamiliesEligible": { "message": "Jij en je familie komen in aanmerking voor gratis Bitwarden Families. Verzilver met je persoonlijke e-mail om je gegevens veilig te houden, zelfs als je niet op het werk bent." }, @@ -6320,6 +6329,18 @@ "sponsoredFamiliesSharedCollections": { "message": "Gedeelde collecties voor familiegeheimen" }, + "memberFamilies": { + "message": "Lid-families" + }, + "noMemberFamilies": { + "message": "Niet-leden families" + }, + "noMemberFamiliesDescription": { + "message": "Leden die familieplannen hebben geclaimd worden hier weergegeven" + }, + "membersWithSponsoredFamilies": { + "message": "Leden van jouw organisatie komen in aanmerking voor gratis gebruik van Bitwarden Familie. Hier kunt je leden zien die een Familie-organisatie hebben gesponsord." + }, "badToken": { "message": "De link is niet langer geldig. Zorg ervoor dat de sponsors de uitnodiging opnieuw versturen." }, @@ -7249,6 +7270,9 @@ "followTheStepsBelowToFinishLoggingIn": { "message": "Volg de onderstaande stappen om in te loggen." }, + "followTheStepsBelowToFinishLoggingInWithSecurityKey": { + "message": "Volg onderstaande stappen om in te loggen met je beveiligingssleutel." + }, "launchDuo": { "message": "DUO starten" }, @@ -7983,6 +8007,9 @@ "inviteMember": { "message": "Lid uitnodigen" }, + "addSponsorship": { + "message": "Sponsoring toevoegen" + }, "needsConfirmation": { "message": "Bevestiging nodig" }, @@ -10571,20 +10598,11 @@ "removeUnlockWithPinPolicyDesc": { "message": "Leden niet toestaan hun account te ontgrendelen met een pincode." }, - "limitedEventLogs": { - "message": "$PRODUCT_TYPE$-abonnementen hebben geen toegang tot echte event logs", - "placeholders": { - "product_type": { - "content": "$1", - "example": "Teams" - } - } + "upgradeForFullEventsMessage": { + "message": "Event logs are not stored for your organization. Upgrade to a Teams or Enterprise plan to get full access to organization event logs." }, - "upgradeForFullEvents": { - "message": "Krijg volledige toegang tot event logs van de organisatie door te upgraden naar een Teams- of Enterprise-abonnement." - }, - "upgradeEventLogTitle": { - "message": "Upgrade voor echte event log gegevens" + "upgradeEventLogTitleMessage": { + "message": "Upgrade to see event logs from your organization." }, "upgradeEventLogMessage": { "message": "Deze evenementen zijn alleen voorbeelden en weerspiegelen geen echte evenementen binnen je Bitwarden organisatie." @@ -10600,5 +10618,8 @@ }, "newBusinessUnit": { "message": "Nieuwe bedrijfseenheid" + }, + "restart": { + "message": "Herstarten" } } diff --git a/apps/web/src/locales/nn/messages.json b/apps/web/src/locales/nn/messages.json index 41d012ac3e9..a886aad81f3 100644 --- a/apps/web/src/locales/nn/messages.json +++ b/apps/web/src/locales/nn/messages.json @@ -2,6 +2,9 @@ "allApplications": { "message": "All applications" }, + "appLogoLabel": { + "message": "Bitwarden logo" + }, "criticalApplications": { "message": "Critical applications" }, @@ -419,18 +422,9 @@ "folder": { "message": "Mappe" }, - "newCustomField": { - "message": "Nytt eigendefinert felt" - }, "value": { "message": "Verdi" }, - "dragToSort": { - "message": "Dra for å sortera" - }, - "dragToReorder": { - "message": "Drag to reorder" - }, "cfTypeText": { "message": "Tekst" }, @@ -4072,8 +4066,8 @@ "updateBrowser": { "message": "Update browser" }, - "generatingRiskInsights": { - "message": "Generating your risk insights..." + "generatingYourRiskInsights": { + "message": "Generating your Risk Insights..." }, "updateBrowserDesc": { "message": "You are using an unsupported web browser. The web vault may not function properly." @@ -6305,6 +6299,21 @@ "sponsoredFamilies": { "message": "Free Bitwarden Families" }, + "sponsoredBitwardenFamilies": { + "message": "Sponsored families" + }, + "noSponsoredFamilies": { + "message": "No sponsored families" + }, + "noSponsoredFamiliesDescription": { + "message": "Sponsored non-member families plans will display here" + }, + "sponsorFreeBitwardenFamilies": { + "message": "Members of your organization are eligible for Free Bitwarden Families. You can sponsor Free Bitwarden Families for employees who are not a member of your Bitwarden organization. Sponsoring a non-member requires an available seat within your organization." + }, + "sponsoredFamiliesRemoveActiveSponsorship": { + "message": "When you remove an active sponsorship, a seat within your organization will be available after the renewal date of the sponsored organization." + }, "sponsoredFamiliesEligible": { "message": "You and your family are eligible for Free Bitwarden Families. Redeem with your personal email to keep your data secure even when you are not at work." }, @@ -6320,6 +6329,18 @@ "sponsoredFamiliesSharedCollections": { "message": "Shared collections for Family secrets" }, + "memberFamilies": { + "message": "Member families" + }, + "noMemberFamilies": { + "message": "No member families" + }, + "noMemberFamiliesDescription": { + "message": "Members who have redeemed family plans will display here" + }, + "membersWithSponsoredFamilies": { + "message": "Members of your organization are eligible for Free Bitwarden Families. Here you can see members who have sponsored a Families organization." + }, "badToken": { "message": "The link is no longer valid. Please have the sponsor resend the offer." }, @@ -7249,6 +7270,9 @@ "followTheStepsBelowToFinishLoggingIn": { "message": "Follow the steps below to finish logging in." }, + "followTheStepsBelowToFinishLoggingInWithSecurityKey": { + "message": "Follow the steps below to finish logging in with your security key." + }, "launchDuo": { "message": "Launch Duo" }, @@ -7983,6 +8007,9 @@ "inviteMember": { "message": "Invite member" }, + "addSponsorship": { + "message": "Add sponsorship" + }, "needsConfirmation": { "message": "Needs confirmation" }, @@ -10571,20 +10598,11 @@ "removeUnlockWithPinPolicyDesc": { "message": "Do not allow members to unlock their account with a PIN." }, - "limitedEventLogs": { - "message": "$PRODUCT_TYPE$ plans do not have access to real event logs", - "placeholders": { - "product_type": { - "content": "$1", - "example": "Teams" - } - } + "upgradeForFullEventsMessage": { + "message": "Event logs are not stored for your organization. Upgrade to a Teams or Enterprise plan to get full access to organization event logs." }, - "upgradeForFullEvents": { - "message": "Get full access to organization event logs by upgrading to a Teams or Enterprise plan." - }, - "upgradeEventLogTitle": { - "message": "Upgrade for real event log data" + "upgradeEventLogTitleMessage": { + "message": "Upgrade to see event logs from your organization." }, "upgradeEventLogMessage": { "message": "These events are examples only and do not reflect real events within your Bitwarden organization." @@ -10600,5 +10618,8 @@ }, "newBusinessUnit": { "message": "New business unit" + }, + "restart": { + "message": "Restart" } } diff --git a/apps/web/src/locales/or/messages.json b/apps/web/src/locales/or/messages.json index 293b3b0486f..6d8d0a9ca42 100644 --- a/apps/web/src/locales/or/messages.json +++ b/apps/web/src/locales/or/messages.json @@ -2,6 +2,9 @@ "allApplications": { "message": "All applications" }, + "appLogoLabel": { + "message": "Bitwarden logo" + }, "criticalApplications": { "message": "Critical applications" }, @@ -419,18 +422,9 @@ "folder": { "message": "Folder" }, - "newCustomField": { - "message": "New custom field" - }, "value": { "message": "Value" }, - "dragToSort": { - "message": "Drag to sort" - }, - "dragToReorder": { - "message": "Drag to reorder" - }, "cfTypeText": { "message": "Text" }, @@ -4072,8 +4066,8 @@ "updateBrowser": { "message": "Update browser" }, - "generatingRiskInsights": { - "message": "Generating your risk insights..." + "generatingYourRiskInsights": { + "message": "Generating your Risk Insights..." }, "updateBrowserDesc": { "message": "You are using an unsupported web browser. The web vault may not function properly." @@ -6305,6 +6299,21 @@ "sponsoredFamilies": { "message": "Free Bitwarden Families" }, + "sponsoredBitwardenFamilies": { + "message": "Sponsored families" + }, + "noSponsoredFamilies": { + "message": "No sponsored families" + }, + "noSponsoredFamiliesDescription": { + "message": "Sponsored non-member families plans will display here" + }, + "sponsorFreeBitwardenFamilies": { + "message": "Members of your organization are eligible for Free Bitwarden Families. You can sponsor Free Bitwarden Families for employees who are not a member of your Bitwarden organization. Sponsoring a non-member requires an available seat within your organization." + }, + "sponsoredFamiliesRemoveActiveSponsorship": { + "message": "When you remove an active sponsorship, a seat within your organization will be available after the renewal date of the sponsored organization." + }, "sponsoredFamiliesEligible": { "message": "You and your family are eligible for Free Bitwarden Families. Redeem with your personal email to keep your data secure even when you are not at work." }, @@ -6320,6 +6329,18 @@ "sponsoredFamiliesSharedCollections": { "message": "Shared collections for Family secrets" }, + "memberFamilies": { + "message": "Member families" + }, + "noMemberFamilies": { + "message": "No member families" + }, + "noMemberFamiliesDescription": { + "message": "Members who have redeemed family plans will display here" + }, + "membersWithSponsoredFamilies": { + "message": "Members of your organization are eligible for Free Bitwarden Families. Here you can see members who have sponsored a Families organization." + }, "badToken": { "message": "The link is no longer valid. Please have the sponsor resend the offer." }, @@ -7249,6 +7270,9 @@ "followTheStepsBelowToFinishLoggingIn": { "message": "Follow the steps below to finish logging in." }, + "followTheStepsBelowToFinishLoggingInWithSecurityKey": { + "message": "Follow the steps below to finish logging in with your security key." + }, "launchDuo": { "message": "Launch Duo" }, @@ -7983,6 +8007,9 @@ "inviteMember": { "message": "Invite member" }, + "addSponsorship": { + "message": "Add sponsorship" + }, "needsConfirmation": { "message": "Needs confirmation" }, @@ -10571,20 +10598,11 @@ "removeUnlockWithPinPolicyDesc": { "message": "Do not allow members to unlock their account with a PIN." }, - "limitedEventLogs": { - "message": "$PRODUCT_TYPE$ plans do not have access to real event logs", - "placeholders": { - "product_type": { - "content": "$1", - "example": "Teams" - } - } + "upgradeForFullEventsMessage": { + "message": "Event logs are not stored for your organization. Upgrade to a Teams or Enterprise plan to get full access to organization event logs." }, - "upgradeForFullEvents": { - "message": "Get full access to organization event logs by upgrading to a Teams or Enterprise plan." - }, - "upgradeEventLogTitle": { - "message": "Upgrade for real event log data" + "upgradeEventLogTitleMessage": { + "message": "Upgrade to see event logs from your organization." }, "upgradeEventLogMessage": { "message": "These events are examples only and do not reflect real events within your Bitwarden organization." @@ -10600,5 +10618,8 @@ }, "newBusinessUnit": { "message": "New business unit" + }, + "restart": { + "message": "Restart" } } diff --git a/apps/web/src/locales/pl/messages.json b/apps/web/src/locales/pl/messages.json index 7fa157e523c..c900db66147 100644 --- a/apps/web/src/locales/pl/messages.json +++ b/apps/web/src/locales/pl/messages.json @@ -2,6 +2,9 @@ "allApplications": { "message": "Wszystkie aplikacje" }, + "appLogoLabel": { + "message": "Bitwarden logo" + }, "criticalApplications": { "message": "Krytyczne aplikacje" }, @@ -419,18 +422,9 @@ "folder": { "message": "Folder" }, - "newCustomField": { - "message": "Nowe pole niestandardowe" - }, "value": { "message": "Wartość" }, - "dragToSort": { - "message": "Przeciągnij, aby posortować" - }, - "dragToReorder": { - "message": "Przeciągnij, aby zmienić kolejność" - }, "cfTypeText": { "message": "Tekst" }, @@ -4072,7 +4066,7 @@ "updateBrowser": { "message": "Aktualizuj przeglądarkę" }, - "generatingRiskInsights": { + "generatingYourRiskInsights": { "message": "Generowanie informacji o ryzyku..." }, "updateBrowserDesc": { @@ -6305,6 +6299,21 @@ "sponsoredFamilies": { "message": "Darmowy plan rodzinny" }, + "sponsoredBitwardenFamilies": { + "message": "Rodziny sponsorowane" + }, + "noSponsoredFamilies": { + "message": "Brak sponsorowanych rodzin" + }, + "noSponsoredFamiliesDescription": { + "message": "Sponsorowane plany rodzin niebędących członkami będą wyświetlane tutaj" + }, + "sponsorFreeBitwardenFamilies": { + "message": "Członkowie Twojej organizacji kwalifikują się do Free Bitwarden Families. Możesz sponsorować Free Bitwarden Families dla pracowników, którzy nie są członkami Twojej organizacji Bitwarden. Sponsorowanie osoby niebędącej członkiem wymaga dostępnego miejsca w Twojej organizacji." + }, + "sponsoredFamiliesRemoveActiveSponsorship": { + "message": "Gdy aktywny sponsoring zostanie usunięty, miejsce w organizacji stanie się dostępne po dacie odnowienia sponsorowanej organizacji." + }, "sponsoredFamiliesEligible": { "message": "Ty i Twoja rodzina kwalifikujecie się do darmowego planu rodzinnego. Wykorzystaj swój osobisty adres e-mail, aby zabezpieczyć swoje dane nawet wtedy, gdy nie jesteś w pracy." }, @@ -6320,6 +6329,18 @@ "sponsoredFamiliesSharedCollections": { "message": "Udostępnione kolekcje dla sekretów rodziny" }, + "memberFamilies": { + "message": "Rodziny członków" + }, + "noMemberFamilies": { + "message": "Brak rodzin członków" + }, + "noMemberFamiliesDescription": { + "message": "Tutaj wyświetlą się członkowie, którzy wykupili plany rodzinne" + }, + "membersWithSponsoredFamilies": { + "message": "Członkowie Twojej organizacji kwalifikują się do Free Bitwarden Families. Tutaj możesz zobaczyć członków, którzy sponsorowali organizację rodzinną." + }, "badToken": { "message": "Link nie jest już ważny. Poproś sponsora o ponowne wysłanie oferty." }, @@ -7249,6 +7270,9 @@ "followTheStepsBelowToFinishLoggingIn": { "message": "Wykonaj poniższe kroki, by dokończyć logowanie" }, + "followTheStepsBelowToFinishLoggingInWithSecurityKey": { + "message": "Wykonaj poniższe kroki, aby zakończyć logowanie za pomocą klucza bezpieczeństwa." + }, "launchDuo": { "message": "Uruchom DUO" }, @@ -7983,6 +8007,9 @@ "inviteMember": { "message": "Zaproś członka" }, + "addSponsorship": { + "message": "Dodaj sponsorowanie" + }, "needsConfirmation": { "message": "Wymaga potwierdzenia" }, @@ -10571,20 +10598,11 @@ "removeUnlockWithPinPolicyDesc": { "message": "Nie zezwalaj członkom na odblokowanie ich konta za pomocą kodu PIN." }, - "limitedEventLogs": { - "message": "Plany $PRODUCT_TYPE$ nie mają dostępu do dzienników rzeczywistych wydarzeń", - "placeholders": { - "product_type": { - "content": "$1", - "example": "Teams" - } - } + "upgradeForFullEventsMessage": { + "message": "Dzienniki zdarzeń nie są przechowywane dla Twojej organizacji. Ulepsz do planu Teams lub Enterprise, aby uzyskać pełny dostęp do dzienników zdarzeń organizacji." }, - "upgradeForFullEvents": { - "message": "Uzyskaj pełny dostęp do dzienników zdarzeń organizacji poprzez uaktualnienie do planu Teams lub Enterprise." - }, - "upgradeEventLogTitle": { - "message": "Uaktualnij dla rzeczywistych danych dziennika zdarzeń" + "upgradeEventLogTitleMessage": { + "message": "Ulepsz, aby zobaczyć dzienniki zdarzeń z Twojej organizacji." }, "upgradeEventLogMessage": { "message": "Te wydarzenia są tylko przykładami i nie odzwierciedlają rzeczywistych wydarzeń w Twojej organizacji Bitwarden." @@ -10600,5 +10618,8 @@ }, "newBusinessUnit": { "message": "Nowa jednostka biznesowa" + }, + "restart": { + "message": "Zrestartuj" } } diff --git a/apps/web/src/locales/pt_BR/messages.json b/apps/web/src/locales/pt_BR/messages.json index 70cbc35fe6c..0f123e12d63 100644 --- a/apps/web/src/locales/pt_BR/messages.json +++ b/apps/web/src/locales/pt_BR/messages.json @@ -2,6 +2,9 @@ "allApplications": { "message": "Todas as aplicações" }, + "appLogoLabel": { + "message": "Bitwarden logo" + }, "criticalApplications": { "message": "Aplicações críticas" }, @@ -419,18 +422,9 @@ "folder": { "message": "Pasta" }, - "newCustomField": { - "message": "Novo Campo Personalizado" - }, "value": { "message": "Valor" }, - "dragToSort": { - "message": "Arrastar para ordenar" - }, - "dragToReorder": { - "message": "Arraste para reorganizar" - }, "cfTypeText": { "message": "Texto" }, @@ -4072,8 +4066,8 @@ "updateBrowser": { "message": "Atualizar Navegador" }, - "generatingRiskInsights": { - "message": "Gerando seu panorama de risco..." + "generatingYourRiskInsights": { + "message": "Generating your Risk Insights..." }, "updateBrowserDesc": { "message": "Você está usando um navegador da Web não suportado. O cofre web pode não funcionar corretamente." @@ -6305,6 +6299,21 @@ "sponsoredFamilies": { "message": "Bitwarden Families Gratuito" }, + "sponsoredBitwardenFamilies": { + "message": "Sponsored families" + }, + "noSponsoredFamilies": { + "message": "No sponsored families" + }, + "noSponsoredFamiliesDescription": { + "message": "Sponsored non-member families plans will display here" + }, + "sponsorFreeBitwardenFamilies": { + "message": "Members of your organization are eligible for Free Bitwarden Families. You can sponsor Free Bitwarden Families for employees who are not a member of your Bitwarden organization. Sponsoring a non-member requires an available seat within your organization." + }, + "sponsoredFamiliesRemoveActiveSponsorship": { + "message": "When you remove an active sponsorship, a seat within your organization will be available after the renewal date of the sponsored organization." + }, "sponsoredFamiliesEligible": { "message": "Você e sua família estão elegíveis para o Bitwarden Families Gratuito. Resgate com seu e-mail pessoal para manter seus dados seguros mesmo quando você não estiver no trabalho." }, @@ -6320,6 +6329,18 @@ "sponsoredFamiliesSharedCollections": { "message": "Coleções compartilhadas de segredos de Família" }, + "memberFamilies": { + "message": "Member families" + }, + "noMemberFamilies": { + "message": "No member families" + }, + "noMemberFamiliesDescription": { + "message": "Members who have redeemed family plans will display here" + }, + "membersWithSponsoredFamilies": { + "message": "Members of your organization are eligible for Free Bitwarden Families. Here you can see members who have sponsored a Families organization." + }, "badToken": { "message": "O link não é mais válido. Peça ao patrocinador para reenviar a oferta." }, @@ -7249,6 +7270,9 @@ "followTheStepsBelowToFinishLoggingIn": { "message": "Siga os passos abaixo para finalizar o login." }, + "followTheStepsBelowToFinishLoggingInWithSecurityKey": { + "message": "Follow the steps below to finish logging in with your security key." + }, "launchDuo": { "message": "Abrir o Duo" }, @@ -7983,6 +8007,9 @@ "inviteMember": { "message": "Convidar membro" }, + "addSponsorship": { + "message": "Add sponsorship" + }, "needsConfirmation": { "message": "Precisa de confirmação" }, @@ -10571,20 +10598,11 @@ "removeUnlockWithPinPolicyDesc": { "message": "Não permitir que os membros desbloqueiem sua conta com um PIN." }, - "limitedEventLogs": { - "message": "Os planos $PRODUCT_TYPE$ não têm acesso aos registros de eventos reais", - "placeholders": { - "product_type": { - "content": "$1", - "example": "Teams" - } - } + "upgradeForFullEventsMessage": { + "message": "Event logs are not stored for your organization. Upgrade to a Teams or Enterprise plan to get full access to organization event logs." }, - "upgradeForFullEvents": { - "message": "Obtenha acesso completo aos logs de eventos da organização atualizando para um plano Times ou Empresarial." - }, - "upgradeEventLogTitle": { - "message": "Atualizar para dados reais do registro de eventos" + "upgradeEventLogTitleMessage": { + "message": "Upgrade to see event logs from your organization." }, "upgradeEventLogMessage": { "message": "Esses eventos são apenas exemplos e não refletem eventos reais na sua organização do Bitwarden." @@ -10593,12 +10611,15 @@ "message": "Organizações gratuitas podem ter até duas coleções. Faça o upgrade para um plano pago para adicionar mais coleções." }, "businessUnit": { - "message": "Business Unit" + "message": "Unidades de Negócio" }, "businessUnits": { - "message": "Business Units" + "message": "Unidades de Negócio" }, "newBusinessUnit": { - "message": "New business unit" + "message": "Nova unidade de negócio" + }, + "restart": { + "message": "Restart" } } diff --git a/apps/web/src/locales/pt_PT/messages.json b/apps/web/src/locales/pt_PT/messages.json index 3840d5fd0fd..8163493d252 100644 --- a/apps/web/src/locales/pt_PT/messages.json +++ b/apps/web/src/locales/pt_PT/messages.json @@ -2,6 +2,9 @@ "allApplications": { "message": "Todas as aplicações" }, + "appLogoLabel": { + "message": "Logótipo do Bitwarden" + }, "criticalApplications": { "message": "Aplicações críticas" }, @@ -419,18 +422,9 @@ "folder": { "message": "Pasta" }, - "newCustomField": { - "message": "Novo campo personalizado" - }, "value": { "message": "Valor" }, - "dragToSort": { - "message": "Arraste para ordenar" - }, - "dragToReorder": { - "message": "Arraste para reordenar" - }, "cfTypeText": { "message": "Texto" }, @@ -441,7 +435,7 @@ "message": "Booleano" }, "cfTypeCheckbox": { - "message": "Checkbox" + "message": "Caixa de verificação" }, "cfTypeLinked": { "message": "Associado", @@ -4072,7 +4066,7 @@ "updateBrowser": { "message": "Atualizar navegador" }, - "generatingRiskInsights": { + "generatingYourRiskInsights": { "message": "A gerar as suas perceções de riscos..." }, "updateBrowserDesc": { @@ -6305,6 +6299,21 @@ "sponsoredFamilies": { "message": "Plano Familiar gratuito Bitwarden" }, + "sponsoredBitwardenFamilies": { + "message": "Planos familiares patrocinados" + }, + "noSponsoredFamilies": { + "message": "Sem planos familiares patrocinados" + }, + "noSponsoredFamiliesDescription": { + "message": "Os planos familiares patrocinados de não membros serão apresentados aqui" + }, + "sponsorFreeBitwardenFamilies": { + "message": "Os membros da sua organização são elegíveis para o plano Familiar Bitwarden gratuito. Pode patrocinar planos Familiares Bitwarden gratuitos para empregados que não sejam membros da sua organização Bitwarden. O patrocínio de um não membro requer um lugar disponível na sua organização." + }, + "sponsoredFamiliesRemoveActiveSponsorship": { + "message": "Quando remover um patrocínio ativo, ficará disponível um lugar na sua organização após a data de renovação da organização patrocinada." + }, "sponsoredFamiliesEligible": { "message": "O utilizador e a sua família são elegíveis para o plano Familiar gratuito Bitwarden. Resgate com o seu e-mail pessoal para manter os seus dados seguros mesmo quando não estiver no trabalho." }, @@ -6320,6 +6329,18 @@ "sponsoredFamiliesSharedCollections": { "message": "Coleções partilhadas para segredos familiares" }, + "memberFamilies": { + "message": "Membros do plano familiar" + }, + "noMemberFamilies": { + "message": "Sem membros do plano familiar" + }, + "noMemberFamiliesDescription": { + "message": "Os membros que tenham resgatado planos familiares serão apresentados aqui" + }, + "membersWithSponsoredFamilies": { + "message": "Os membros da sua organização são elegíveis para os planos Familiares Bitwarden gratuitos. Aqui pode ver os membros que patrocinaram uma organização Familiar." + }, "badToken": { "message": "O link já não é válido. Peça ao patrocinador para reenviar a oferta." }, @@ -7249,6 +7270,9 @@ "followTheStepsBelowToFinishLoggingIn": { "message": "Siga os passos abaixo para concluir o início de sessão." }, + "followTheStepsBelowToFinishLoggingInWithSecurityKey": { + "message": "Siga os passos abaixo para concluir o início de sessão com a sua chave de segurança." + }, "launchDuo": { "message": "Iniciar o DUO" }, @@ -7983,6 +8007,9 @@ "inviteMember": { "message": "Convidar membro" }, + "addSponsorship": { + "message": "Adicionar patrocínio" + }, "needsConfirmation": { "message": "Precisa de confirmação" }, @@ -10571,20 +10598,11 @@ "removeUnlockWithPinPolicyDesc": { "message": "Não permitir que os membros desbloqueiem a sua conta com um PIN." }, - "limitedEventLogs": { - "message": "Os planos $PRODUCT_TYPE$ não têm acesso a registos de eventos reais", - "placeholders": { - "product_type": { - "content": "$1", - "example": "Teams" - } - } + "upgradeForFullEventsMessage": { + "message": "Os registos de eventos não são armazenados para a sua organização. Atualize para um plano Equipas ou Empresarial para obter acesso total aos registos de eventos da organização." }, - "upgradeForFullEvents": { - "message": "Obtenha acesso total aos registos de eventos da organização ao atualizar para um plano Equipas ou Empresarial." - }, - "upgradeEventLogTitle": { - "message": "Atualizar para dados de registo de eventos reais" + "upgradeEventLogTitleMessage": { + "message": "Atualize para ver os registos de eventos da sua organização." }, "upgradeEventLogMessage": { "message": "Estes eventos são apenas exemplos e não refletem eventos reais na sua organização Bitwarden." @@ -10600,5 +10618,8 @@ }, "newBusinessUnit": { "message": "Nova unidade de negócio" + }, + "restart": { + "message": "Reiniciar" } } diff --git a/apps/web/src/locales/ro/messages.json b/apps/web/src/locales/ro/messages.json index e513deb7f5e..25d24f72d54 100644 --- a/apps/web/src/locales/ro/messages.json +++ b/apps/web/src/locales/ro/messages.json @@ -2,6 +2,9 @@ "allApplications": { "message": "Toate aplicațiile" }, + "appLogoLabel": { + "message": "Bitwarden logo" + }, "criticalApplications": { "message": "Aplicațiile critice" }, @@ -419,18 +422,9 @@ "folder": { "message": "Dosar" }, - "newCustomField": { - "message": "Câmp nou particularizat" - }, "value": { "message": "Valoare" }, - "dragToSort": { - "message": "Tragere pentru sortare" - }, - "dragToReorder": { - "message": "Drag to reorder" - }, "cfTypeText": { "message": "Text" }, @@ -4072,8 +4066,8 @@ "updateBrowser": { "message": "Actualizare browser" }, - "generatingRiskInsights": { - "message": "Generating your risk insights..." + "generatingYourRiskInsights": { + "message": "Generating your Risk Insights..." }, "updateBrowserDesc": { "message": "Utilizați un browser nesuportat. Seiful web ar putea să nu funcționeze corect." @@ -6305,6 +6299,21 @@ "sponsoredFamilies": { "message": "Planul Bitwarden Familii gratuit" }, + "sponsoredBitwardenFamilies": { + "message": "Sponsored families" + }, + "noSponsoredFamilies": { + "message": "No sponsored families" + }, + "noSponsoredFamiliesDescription": { + "message": "Sponsored non-member families plans will display here" + }, + "sponsorFreeBitwardenFamilies": { + "message": "Members of your organization are eligible for Free Bitwarden Families. You can sponsor Free Bitwarden Families for employees who are not a member of your Bitwarden organization. Sponsoring a non-member requires an available seat within your organization." + }, + "sponsoredFamiliesRemoveActiveSponsorship": { + "message": "When you remove an active sponsorship, a seat within your organization will be available after the renewal date of the sponsored organization." + }, "sponsoredFamiliesEligible": { "message": "Dumneavoastră și familia dvs., sunteți eligibili pentru planul Bitwarden Familii gratuit. Revendicați-l cu e-mailul personal pentru a vă păstra datele în siguranță chiar și atunci când nu sunteți la locul de muncă." }, @@ -6320,6 +6329,18 @@ "sponsoredFamiliesSharedCollections": { "message": "Colecții partajate pentru secrete familiale" }, + "memberFamilies": { + "message": "Member families" + }, + "noMemberFamilies": { + "message": "No member families" + }, + "noMemberFamiliesDescription": { + "message": "Members who have redeemed family plans will display here" + }, + "membersWithSponsoredFamilies": { + "message": "Members of your organization are eligible for Free Bitwarden Families. Here you can see members who have sponsored a Families organization." + }, "badToken": { "message": "Linkul nu mai este valabil. Vă rugăm să cereți sponsorului să retrimită oferta." }, @@ -7249,6 +7270,9 @@ "followTheStepsBelowToFinishLoggingIn": { "message": "Follow the steps below to finish logging in." }, + "followTheStepsBelowToFinishLoggingInWithSecurityKey": { + "message": "Follow the steps below to finish logging in with your security key." + }, "launchDuo": { "message": "Launch Duo" }, @@ -7983,6 +8007,9 @@ "inviteMember": { "message": "Invite member" }, + "addSponsorship": { + "message": "Add sponsorship" + }, "needsConfirmation": { "message": "Needs confirmation" }, @@ -10571,20 +10598,11 @@ "removeUnlockWithPinPolicyDesc": { "message": "Do not allow members to unlock their account with a PIN." }, - "limitedEventLogs": { - "message": "$PRODUCT_TYPE$ plans do not have access to real event logs", - "placeholders": { - "product_type": { - "content": "$1", - "example": "Teams" - } - } + "upgradeForFullEventsMessage": { + "message": "Event logs are not stored for your organization. Upgrade to a Teams or Enterprise plan to get full access to organization event logs." }, - "upgradeForFullEvents": { - "message": "Get full access to organization event logs by upgrading to a Teams or Enterprise plan." - }, - "upgradeEventLogTitle": { - "message": "Upgrade for real event log data" + "upgradeEventLogTitleMessage": { + "message": "Upgrade to see event logs from your organization." }, "upgradeEventLogMessage": { "message": "These events are examples only and do not reflect real events within your Bitwarden organization." @@ -10600,5 +10618,8 @@ }, "newBusinessUnit": { "message": "New business unit" + }, + "restart": { + "message": "Restart" } } diff --git a/apps/web/src/locales/ru/messages.json b/apps/web/src/locales/ru/messages.json index e84af0d0b54..eb9e2aa6fd3 100644 --- a/apps/web/src/locales/ru/messages.json +++ b/apps/web/src/locales/ru/messages.json @@ -2,6 +2,9 @@ "allApplications": { "message": "Все приложения" }, + "appLogoLabel": { + "message": "Логотип Bitwarden" + }, "criticalApplications": { "message": "Критичные приложения" }, @@ -419,18 +422,9 @@ "folder": { "message": "Папка" }, - "newCustomField": { - "message": "Новое пользовательское поле" - }, "value": { "message": "Значение" }, - "dragToSort": { - "message": "Перетащите для сортировки" - }, - "dragToReorder": { - "message": "Перетащите для изменения порядка" - }, "cfTypeText": { "message": "Текстовое" }, @@ -4072,8 +4066,8 @@ "updateBrowser": { "message": "Обновить браузер" }, - "generatingRiskInsights": { - "message": "Генерирование информации о рисках..." + "generatingYourRiskInsights": { + "message": "Генерация информации о рисках..." }, "updateBrowserDesc": { "message": "Вы используете неподдерживаемый браузер. Веб-хранилище может работать некорректно." @@ -6305,6 +6299,21 @@ "sponsoredFamilies": { "message": "Бесплатный план Bitwarden Families" }, + "sponsoredBitwardenFamilies": { + "message": "Спонсируемые семьи" + }, + "noSponsoredFamilies": { + "message": "Нет спонсируемых семей" + }, + "noSponsoredFamiliesDescription": { + "message": "Sponsored non-member families plans will display here" + }, + "sponsorFreeBitwardenFamilies": { + "message": "Members of your organization are eligible for Free Bitwarden Families. You can sponsor Free Bitwarden Families for employees who are not a member of your Bitwarden organization. Sponsoring a non-member requires an available seat within your organization." + }, + "sponsoredFamiliesRemoveActiveSponsorship": { + "message": "When you remove an active sponsorship, a seat within your organization will be available after the renewal date of the sponsored organization." + }, "sponsoredFamiliesEligible": { "message": "Вам и вашей семье доступен бесплатный план Bitwarden Families. Используйте свой личный адрес электронной почты, чтобы защитить данные даже тогда, когда вы не на работе." }, @@ -6320,6 +6329,18 @@ "sponsoredFamiliesSharedCollections": { "message": "Общие коллекции для семейных секретов" }, + "memberFamilies": { + "message": "Члены семей" + }, + "noMemberFamilies": { + "message": "Нет членов семей" + }, + "noMemberFamiliesDescription": { + "message": "Members who have redeemed family plans will display here" + }, + "membersWithSponsoredFamilies": { + "message": "Members of your organization are eligible for Free Bitwarden Families. Here you can see members who have sponsored a Families organization." + }, "badToken": { "message": "Ссылка больше не действительна. Пожалуйста, попросите спонсора повторно отправить предложение." }, @@ -7249,6 +7270,9 @@ "followTheStepsBelowToFinishLoggingIn": { "message": "Следуйте указаниям ниже, чтобы завершить авторизацию." }, + "followTheStepsBelowToFinishLoggingInWithSecurityKey": { + "message": "Выполните следующие шаги, чтобы завершить авторизацию с помощью ключа безопасности." + }, "launchDuo": { "message": "Запустить Duo" }, @@ -7983,6 +8007,9 @@ "inviteMember": { "message": "Пригласить участника" }, + "addSponsorship": { + "message": "Добавить спонсорство" + }, "needsConfirmation": { "message": "Требуется подтверждение" }, @@ -10571,20 +10598,11 @@ "removeUnlockWithPinPolicyDesc": { "message": "Не разрешать пользователям разблокировать свои аккаунты с помощью PIN-кода." }, - "limitedEventLogs": { - "message": "Планы $PRODUCT_TYPE$ не имеют доступа к журналам текущих событий", - "placeholders": { - "product_type": { - "content": "$1", - "example": "Teams" - } - } + "upgradeForFullEventsMessage": { + "message": "Логи событий не сохраняются для вашей организации. Перейдите на тариф Teams или Enterprise для получения полного доступа к логам событий организации." }, - "upgradeForFullEvents": { - "message": "Получите полный доступ к журналам событий организации, перейдя на план Teams или Enterprise." - }, - "upgradeEventLogTitle": { - "message": "Переудите на более высокий тариф для просмотра журнала реальных событий" + "upgradeEventLogTitleMessage": { + "message": "Измените тариф для просмотра логов событий вашей организации." }, "upgradeEventLogMessage": { "message": "Эти события являются лишь примерами и не отражают текущих событий вашей организации Bitwarden." @@ -10600,5 +10618,8 @@ }, "newBusinessUnit": { "message": "Новая бизнес-единица" + }, + "restart": { + "message": "Перезапустить" } } diff --git a/apps/web/src/locales/si/messages.json b/apps/web/src/locales/si/messages.json index 2acc04e756d..8d65e96aa53 100644 --- a/apps/web/src/locales/si/messages.json +++ b/apps/web/src/locales/si/messages.json @@ -2,6 +2,9 @@ "allApplications": { "message": "All applications" }, + "appLogoLabel": { + "message": "Bitwarden logo" + }, "criticalApplications": { "message": "Critical applications" }, @@ -419,18 +422,9 @@ "folder": { "message": "බහාලුම" }, - "newCustomField": { - "message": "New custom field" - }, "value": { "message": "අගය" }, - "dragToSort": { - "message": "Drag to sort" - }, - "dragToReorder": { - "message": "Drag to reorder" - }, "cfTypeText": { "message": "Text" }, @@ -4072,8 +4066,8 @@ "updateBrowser": { "message": "Update browser" }, - "generatingRiskInsights": { - "message": "Generating your risk insights..." + "generatingYourRiskInsights": { + "message": "Generating your Risk Insights..." }, "updateBrowserDesc": { "message": "You are using an unsupported web browser. The web vault may not function properly." @@ -6305,6 +6299,21 @@ "sponsoredFamilies": { "message": "Free Bitwarden Families" }, + "sponsoredBitwardenFamilies": { + "message": "Sponsored families" + }, + "noSponsoredFamilies": { + "message": "No sponsored families" + }, + "noSponsoredFamiliesDescription": { + "message": "Sponsored non-member families plans will display here" + }, + "sponsorFreeBitwardenFamilies": { + "message": "Members of your organization are eligible for Free Bitwarden Families. You can sponsor Free Bitwarden Families for employees who are not a member of your Bitwarden organization. Sponsoring a non-member requires an available seat within your organization." + }, + "sponsoredFamiliesRemoveActiveSponsorship": { + "message": "When you remove an active sponsorship, a seat within your organization will be available after the renewal date of the sponsored organization." + }, "sponsoredFamiliesEligible": { "message": "You and your family are eligible for Free Bitwarden Families. Redeem with your personal email to keep your data secure even when you are not at work." }, @@ -6320,6 +6329,18 @@ "sponsoredFamiliesSharedCollections": { "message": "Shared collections for Family secrets" }, + "memberFamilies": { + "message": "Member families" + }, + "noMemberFamilies": { + "message": "No member families" + }, + "noMemberFamiliesDescription": { + "message": "Members who have redeemed family plans will display here" + }, + "membersWithSponsoredFamilies": { + "message": "Members of your organization are eligible for Free Bitwarden Families. Here you can see members who have sponsored a Families organization." + }, "badToken": { "message": "The link is no longer valid. Please have the sponsor resend the offer." }, @@ -7249,6 +7270,9 @@ "followTheStepsBelowToFinishLoggingIn": { "message": "Follow the steps below to finish logging in." }, + "followTheStepsBelowToFinishLoggingInWithSecurityKey": { + "message": "Follow the steps below to finish logging in with your security key." + }, "launchDuo": { "message": "Launch Duo" }, @@ -7983,6 +8007,9 @@ "inviteMember": { "message": "Invite member" }, + "addSponsorship": { + "message": "Add sponsorship" + }, "needsConfirmation": { "message": "Needs confirmation" }, @@ -10571,20 +10598,11 @@ "removeUnlockWithPinPolicyDesc": { "message": "Do not allow members to unlock their account with a PIN." }, - "limitedEventLogs": { - "message": "$PRODUCT_TYPE$ plans do not have access to real event logs", - "placeholders": { - "product_type": { - "content": "$1", - "example": "Teams" - } - } + "upgradeForFullEventsMessage": { + "message": "Event logs are not stored for your organization. Upgrade to a Teams or Enterprise plan to get full access to organization event logs." }, - "upgradeForFullEvents": { - "message": "Get full access to organization event logs by upgrading to a Teams or Enterprise plan." - }, - "upgradeEventLogTitle": { - "message": "Upgrade for real event log data" + "upgradeEventLogTitleMessage": { + "message": "Upgrade to see event logs from your organization." }, "upgradeEventLogMessage": { "message": "These events are examples only and do not reflect real events within your Bitwarden organization." @@ -10600,5 +10618,8 @@ }, "newBusinessUnit": { "message": "New business unit" + }, + "restart": { + "message": "Restart" } } diff --git a/apps/web/src/locales/sk/messages.json b/apps/web/src/locales/sk/messages.json index 0c3c4eaba26..71a0de95006 100644 --- a/apps/web/src/locales/sk/messages.json +++ b/apps/web/src/locales/sk/messages.json @@ -2,6 +2,9 @@ "allApplications": { "message": "Všetky aplikácie" }, + "appLogoLabel": { + "message": "Logo Bitwarden" + }, "criticalApplications": { "message": "Kritické aplikácie" }, @@ -419,18 +422,9 @@ "folder": { "message": "Priečinok" }, - "newCustomField": { - "message": "Nové vlastné pole" - }, "value": { "message": "Hodnota" }, - "dragToSort": { - "message": "Zoradiť presúvaním" - }, - "dragToReorder": { - "message": "Potiahnutím zmeníte poradie" - }, "cfTypeText": { "message": "Text" }, @@ -3324,10 +3318,10 @@ "message": "Externé Id sa môže použiť na previazanie tohto zdroja s externým systémom - napríklad s užívateľským adresárom." }, "ssoExternalId": { - "message": "SSO External ID" + "message": "Externé ID SSO" }, "ssoExternalIdDesc": { - "message": "SSO External ID is an unencrypted reference between Bitwarden and your configured SSO provider." + "message": "Externé ID SSO je nešifrovaný odkaz medzi Bitwardenom a nakonfigurovaným poskytovateľom SSO." }, "nestCollectionUnder": { "message": "Zaradiť zbierku pod" @@ -4072,7 +4066,7 @@ "updateBrowser": { "message": "Aktualizovať prehliadač" }, - "generatingRiskInsights": { + "generatingYourRiskInsights": { "message": "Generuje sa váš prehľad o rizikách..." }, "updateBrowserDesc": { @@ -5692,7 +5686,7 @@ "message": "WebAuthn bol úspešne overený! Túto kartu môžete zavrieť." }, "yourNewPasswordCannotBeTheSameAsYourCurrentPassword": { - "message": "Your new password cannot be the same as your current password." + "message": "Nové heslo nemôže byť rovnaké ako súčasné heslo." }, "hintEqualsPassword": { "message": "Nápoveda pre heslo nemôže byť rovnaká ako heslo." @@ -6305,6 +6299,21 @@ "sponsoredFamilies": { "message": "Bitwarden pre Rodiny zadarmo" }, + "sponsoredBitwardenFamilies": { + "message": "Sponzorované rodiny" + }, + "noSponsoredFamilies": { + "message": "Žiadne sponzorované rodiny" + }, + "noSponsoredFamiliesDescription": { + "message": "Tu sa zobrazia sponzorované plány pre Rodiny pre nečlenov" + }, + "sponsorFreeBitwardenFamilies": { + "message": "Členovia vašej organizácie majú nárok na bezplatný plán pre Rodiny. Bezplatný plán pre Rodiny môžete sponzorovať aj pre zamestnancov ktorí nie sú členmi vašej Bitwarden organizácie. Spozorovanie nečlena vyžaduje voľné sedenie v organizácii." + }, + "sponsoredFamiliesRemoveActiveSponsorship": { + "message": "Keď odoberiete aktívne sponzorstvo, sedenie vo vašej organizácii sa uvoľní po dátume obnovenia sponzorovanej organizácie." + }, "sponsoredFamiliesEligible": { "message": "Vy a vaša rodina máte bezplatne k dispozícii Bitwarden pre Rodiny. Uplatnite si túto možnosť s vaším osobným emailom aby ste mali dáta v bezpečí aj keď nie ste v práci." }, @@ -6320,6 +6329,18 @@ "sponsoredFamiliesSharedCollections": { "message": "Zdieľané zbierky pre Rodinné heslá" }, + "memberFamilies": { + "message": "Členské rodiny" + }, + "noMemberFamilies": { + "message": "Nečlenské rodiny" + }, + "noMemberFamiliesDescription": { + "message": "Tu sa zobrazia členovia ktorí využili rodinne plány" + }, + "membersWithSponsoredFamilies": { + "message": "Členovia vašej organizácie majú nárok na bezplatný plán pre Rodiny. Tu môžete vidieť členov so sponzorstvom Rodinnej organizácie." + }, "badToken": { "message": "The link is no longer valid. Please have the sponsor resend the offer." }, @@ -7249,6 +7270,9 @@ "followTheStepsBelowToFinishLoggingIn": { "message": "Na dokončenie prihlásenia postupujte podľa pokynov." }, + "followTheStepsBelowToFinishLoggingInWithSecurityKey": { + "message": "Podľa nasledujúcich krokov dokončite prihlásenie pomocou bezpečnostného kľúča." + }, "launchDuo": { "message": "Spustiť DUO" }, @@ -7983,6 +8007,9 @@ "inviteMember": { "message": "Pozvať člena" }, + "addSponsorship": { + "message": "Pridať sponzorstvo" + }, "needsConfirmation": { "message": "Potrebné potvrdenie" }, @@ -10571,20 +10598,11 @@ "removeUnlockWithPinPolicyDesc": { "message": "Neumožnite vaším členom odomykať si konto PIN kódom." }, - "limitedEventLogs": { - "message": "$PRODUCT_TYPE$ plans do not have access to real event logs", - "placeholders": { - "product_type": { - "content": "$1", - "example": "Teams" - } - } + "upgradeForFullEventsMessage": { + "message": "Event logs are not stored for your organization. Upgrade to a Teams or Enterprise plan to get full access to organization event logs." }, - "upgradeForFullEvents": { - "message": "Prechodom na plán Teams alebo Enterprise získate úplný prístup k denníku udalostí organizácie." - }, - "upgradeEventLogTitle": { - "message": "Upgrade for real event log data" + "upgradeEventLogTitleMessage": { + "message": "Upgrade to see event logs from your organization." }, "upgradeEventLogMessage": { "message": "These events are examples only and do not reflect real events within your Bitwarden organization." @@ -10593,12 +10611,15 @@ "message": "Bezplatné organizácie môžu mat maximálne dve zbierky. Ak chcete pridať viac zbierok povýšte na platené predplatné." }, "businessUnit": { - "message": "Business Unit" + "message": "Organizačná jednotka" }, "businessUnits": { - "message": "Business Units" + "message": "Organizačné jednotky" }, "newBusinessUnit": { - "message": "New business unit" + "message": "Nová organizačná jednotka" + }, + "restart": { + "message": "Restart" } } diff --git a/apps/web/src/locales/sl/messages.json b/apps/web/src/locales/sl/messages.json index fd990234b86..93e436b60dc 100644 --- a/apps/web/src/locales/sl/messages.json +++ b/apps/web/src/locales/sl/messages.json @@ -2,6 +2,9 @@ "allApplications": { "message": "Vse aplikacije" }, + "appLogoLabel": { + "message": "Bitwarden logo" + }, "criticalApplications": { "message": "Kritične aplikacije" }, @@ -419,18 +422,9 @@ "folder": { "message": "Mapa" }, - "newCustomField": { - "message": "Novo polje po meri" - }, "value": { "message": "Vrednost" }, - "dragToSort": { - "message": "Povleci za sortiranje" - }, - "dragToReorder": { - "message": "Drag to reorder" - }, "cfTypeText": { "message": "Besedilo" }, @@ -4072,8 +4066,8 @@ "updateBrowser": { "message": "Update browser" }, - "generatingRiskInsights": { - "message": "Generating your risk insights..." + "generatingYourRiskInsights": { + "message": "Generating your Risk Insights..." }, "updateBrowserDesc": { "message": "You are using an unsupported web browser. The web vault may not function properly." @@ -6305,6 +6299,21 @@ "sponsoredFamilies": { "message": "Free Bitwarden Families" }, + "sponsoredBitwardenFamilies": { + "message": "Sponsored families" + }, + "noSponsoredFamilies": { + "message": "No sponsored families" + }, + "noSponsoredFamiliesDescription": { + "message": "Sponsored non-member families plans will display here" + }, + "sponsorFreeBitwardenFamilies": { + "message": "Members of your organization are eligible for Free Bitwarden Families. You can sponsor Free Bitwarden Families for employees who are not a member of your Bitwarden organization. Sponsoring a non-member requires an available seat within your organization." + }, + "sponsoredFamiliesRemoveActiveSponsorship": { + "message": "When you remove an active sponsorship, a seat within your organization will be available after the renewal date of the sponsored organization." + }, "sponsoredFamiliesEligible": { "message": "You and your family are eligible for Free Bitwarden Families. Redeem with your personal email to keep your data secure even when you are not at work." }, @@ -6320,6 +6329,18 @@ "sponsoredFamiliesSharedCollections": { "message": "Shared collections for Family secrets" }, + "memberFamilies": { + "message": "Member families" + }, + "noMemberFamilies": { + "message": "No member families" + }, + "noMemberFamiliesDescription": { + "message": "Members who have redeemed family plans will display here" + }, + "membersWithSponsoredFamilies": { + "message": "Members of your organization are eligible for Free Bitwarden Families. Here you can see members who have sponsored a Families organization." + }, "badToken": { "message": "The link is no longer valid. Please have the sponsor resend the offer." }, @@ -7249,6 +7270,9 @@ "followTheStepsBelowToFinishLoggingIn": { "message": "Follow the steps below to finish logging in." }, + "followTheStepsBelowToFinishLoggingInWithSecurityKey": { + "message": "Follow the steps below to finish logging in with your security key." + }, "launchDuo": { "message": "Launch Duo" }, @@ -7983,6 +8007,9 @@ "inviteMember": { "message": "Invite member" }, + "addSponsorship": { + "message": "Add sponsorship" + }, "needsConfirmation": { "message": "Needs confirmation" }, @@ -10571,20 +10598,11 @@ "removeUnlockWithPinPolicyDesc": { "message": "Do not allow members to unlock their account with a PIN." }, - "limitedEventLogs": { - "message": "$PRODUCT_TYPE$ plans do not have access to real event logs", - "placeholders": { - "product_type": { - "content": "$1", - "example": "Teams" - } - } + "upgradeForFullEventsMessage": { + "message": "Event logs are not stored for your organization. Upgrade to a Teams or Enterprise plan to get full access to organization event logs." }, - "upgradeForFullEvents": { - "message": "Get full access to organization event logs by upgrading to a Teams or Enterprise plan." - }, - "upgradeEventLogTitle": { - "message": "Upgrade for real event log data" + "upgradeEventLogTitleMessage": { + "message": "Upgrade to see event logs from your organization." }, "upgradeEventLogMessage": { "message": "These events are examples only and do not reflect real events within your Bitwarden organization." @@ -10600,5 +10618,8 @@ }, "newBusinessUnit": { "message": "New business unit" + }, + "restart": { + "message": "Restart" } } diff --git a/apps/web/src/locales/sr/messages.json b/apps/web/src/locales/sr/messages.json index 1a414d913a7..aa0ab9b2562 100644 --- a/apps/web/src/locales/sr/messages.json +++ b/apps/web/src/locales/sr/messages.json @@ -2,6 +2,9 @@ "allApplications": { "message": "Све апликације" }, + "appLogoLabel": { + "message": "Bitwarden лого" + }, "criticalApplications": { "message": "Критичне апликације" }, @@ -419,18 +422,9 @@ "folder": { "message": "Фасцикла" }, - "newCustomField": { - "message": "Ново прилагођено поље" - }, "value": { "message": "Вредност" }, - "dragToSort": { - "message": "Превуците за сортирање" - }, - "dragToReorder": { - "message": "Превуците да бисте организовали" - }, "cfTypeText": { "message": "Текст" }, @@ -3324,10 +3318,10 @@ "message": "Спољни ид се може користити као референца или за повезивање овог ресурса са спољним системом као што је корисничка фасцикла." }, "ssoExternalId": { - "message": "SSO External ID" + "message": "SSO Спољни ИД" }, "ssoExternalIdDesc": { - "message": "SSO External ID is an unencrypted reference between Bitwarden and your configured SSO provider." + "message": "SSO Спољни ИД је неуредна референца између Bitwarden-а и вашег конфигурисаног SSO провајдера." }, "nestCollectionUnder": { "message": "Постави колекцију испод" @@ -4072,7 +4066,7 @@ "updateBrowser": { "message": "Ажурирајте Претраживач" }, - "generatingRiskInsights": { + "generatingYourRiskInsights": { "message": "Генерисање прегледа вашег ризика..." }, "updateBrowserDesc": { @@ -6305,6 +6299,21 @@ "sponsoredFamilies": { "message": "Бесплатно Bitwarden Families" }, + "sponsoredBitwardenFamilies": { + "message": "Sponsored families" + }, + "noSponsoredFamilies": { + "message": "No sponsored families" + }, + "noSponsoredFamiliesDescription": { + "message": "Sponsored non-member families plans will display here" + }, + "sponsorFreeBitwardenFamilies": { + "message": "Members of your organization are eligible for Free Bitwarden Families. You can sponsor Free Bitwarden Families for employees who are not a member of your Bitwarden organization. Sponsoring a non-member requires an available seat within your organization." + }, + "sponsoredFamiliesRemoveActiveSponsorship": { + "message": "When you remove an active sponsorship, a seat within your organization will be available after the renewal date of the sponsored organization." + }, "sponsoredFamiliesEligible": { "message": "Ви и ваша породица испуњавате услове за бесплатне Bitwarden Families. Искористите својом личном е-поштом да бисте заштитили своје податке чак и када нисте на послу." }, @@ -6320,6 +6329,18 @@ "sponsoredFamiliesSharedCollections": { "message": "Shared collections for Family secrets" }, + "memberFamilies": { + "message": "Member families" + }, + "noMemberFamilies": { + "message": "No member families" + }, + "noMemberFamiliesDescription": { + "message": "Members who have redeemed family plans will display here" + }, + "membersWithSponsoredFamilies": { + "message": "Members of your organization are eligible for Free Bitwarden Families. Here you can see members who have sponsored a Families organization." + }, "badToken": { "message": "Веза више није важећа. Питајте спонзора да поново пошаље понуду." }, @@ -6839,10 +6860,10 @@ "message": "„Ухвати све“ е-порука" }, "catchallEmailDesc": { - "message": "Use your domain's configured catch-all inbox." + "message": "Користите подешено catch-all пријемно сандуче вашег домена." }, "useThisEmail": { - "message": "Use this email" + "message": "Користи овај имејл" }, "random": { "message": "Случајно", @@ -7249,6 +7270,9 @@ "followTheStepsBelowToFinishLoggingIn": { "message": "Follow the steps below to finish logging in." }, + "followTheStepsBelowToFinishLoggingInWithSecurityKey": { + "message": "Follow the steps below to finish logging in with your security key." + }, "launchDuo": { "message": "Покренути DUO" }, @@ -7983,6 +8007,9 @@ "inviteMember": { "message": "Позови Члан" }, + "addSponsorship": { + "message": "Add sponsorship" + }, "needsConfirmation": { "message": "Потребна је потврда" }, @@ -8769,7 +8796,7 @@ "message": "УРЛ Сервера" }, "selfHostBaseUrl": { - "message": "Self-host server URL", + "message": "УРЛ сервера који се самостално хостује", "description": "Label for field requesting a self-hosted integration service URL" }, "alreadyHaveAccount": { @@ -9342,7 +9369,7 @@ "message": "Недостаје датум креације уређаја" }, "desktopRequired": { - "message": "Desktop required" + "message": "Desktop је потребан" }, "reopenLinkOnDesktop": { "message": "Reopen this link from your email on a desktop." @@ -10571,20 +10598,11 @@ "removeUnlockWithPinPolicyDesc": { "message": "Do not allow members to unlock their account with a PIN." }, - "limitedEventLogs": { - "message": "$PRODUCT_TYPE$ планови немају приступ стварним извештајима догађаја", - "placeholders": { - "product_type": { - "content": "$1", - "example": "Teams" - } - } + "upgradeForFullEventsMessage": { + "message": "Event logs are not stored for your organization. Upgrade to a Teams or Enterprise plan to get full access to organization event logs." }, - "upgradeForFullEvents": { - "message": "Добијте потпуни приступ извештајима о догађајима организације надоградњом на Teams или Enterprise." - }, - "upgradeEventLogTitle": { - "message": "Надоградите за извештај о реалним догађајима" + "upgradeEventLogTitleMessage": { + "message": "Upgrade to see event logs from your organization." }, "upgradeEventLogMessage": { "message": "Ови догађаји су само примери и не одражавају стварне догађаје у вашем Bitwarden отганизацији." @@ -10600,5 +10618,8 @@ }, "newBusinessUnit": { "message": "New business unit" + }, + "restart": { + "message": "Restart" } } diff --git a/apps/web/src/locales/sr_CS/messages.json b/apps/web/src/locales/sr_CS/messages.json index 93f2c89a16d..017d5611009 100644 --- a/apps/web/src/locales/sr_CS/messages.json +++ b/apps/web/src/locales/sr_CS/messages.json @@ -2,6 +2,9 @@ "allApplications": { "message": "Sve aplikacije" }, + "appLogoLabel": { + "message": "Bitwarden logo" + }, "criticalApplications": { "message": "Kritične aplikacije" }, @@ -419,18 +422,9 @@ "folder": { "message": "Fascikla" }, - "newCustomField": { - "message": "Novo Prilagođeno Polje" - }, "value": { "message": "Vrednost" }, - "dragToSort": { - "message": "Sortiraj prevlačenjem" - }, - "dragToReorder": { - "message": "Drag to reorder" - }, "cfTypeText": { "message": "Tekst" }, @@ -4072,8 +4066,8 @@ "updateBrowser": { "message": "Update browser" }, - "generatingRiskInsights": { - "message": "Generating your risk insights..." + "generatingYourRiskInsights": { + "message": "Generating your Risk Insights..." }, "updateBrowserDesc": { "message": "You are using an unsupported web browser. The web vault may not function properly." @@ -6305,6 +6299,21 @@ "sponsoredFamilies": { "message": "Free Bitwarden Families" }, + "sponsoredBitwardenFamilies": { + "message": "Sponsored families" + }, + "noSponsoredFamilies": { + "message": "No sponsored families" + }, + "noSponsoredFamiliesDescription": { + "message": "Sponsored non-member families plans will display here" + }, + "sponsorFreeBitwardenFamilies": { + "message": "Members of your organization are eligible for Free Bitwarden Families. You can sponsor Free Bitwarden Families for employees who are not a member of your Bitwarden organization. Sponsoring a non-member requires an available seat within your organization." + }, + "sponsoredFamiliesRemoveActiveSponsorship": { + "message": "When you remove an active sponsorship, a seat within your organization will be available after the renewal date of the sponsored organization." + }, "sponsoredFamiliesEligible": { "message": "You and your family are eligible for Free Bitwarden Families. Redeem with your personal email to keep your data secure even when you are not at work." }, @@ -6320,6 +6329,18 @@ "sponsoredFamiliesSharedCollections": { "message": "Shared collections for Family secrets" }, + "memberFamilies": { + "message": "Member families" + }, + "noMemberFamilies": { + "message": "No member families" + }, + "noMemberFamiliesDescription": { + "message": "Members who have redeemed family plans will display here" + }, + "membersWithSponsoredFamilies": { + "message": "Members of your organization are eligible for Free Bitwarden Families. Here you can see members who have sponsored a Families organization." + }, "badToken": { "message": "The link is no longer valid. Please have the sponsor resend the offer." }, @@ -7249,6 +7270,9 @@ "followTheStepsBelowToFinishLoggingIn": { "message": "Follow the steps below to finish logging in." }, + "followTheStepsBelowToFinishLoggingInWithSecurityKey": { + "message": "Follow the steps below to finish logging in with your security key." + }, "launchDuo": { "message": "Launch Duo" }, @@ -7983,6 +8007,9 @@ "inviteMember": { "message": "Invite member" }, + "addSponsorship": { + "message": "Add sponsorship" + }, "needsConfirmation": { "message": "Needs confirmation" }, @@ -10571,20 +10598,11 @@ "removeUnlockWithPinPolicyDesc": { "message": "Do not allow members to unlock their account with a PIN." }, - "limitedEventLogs": { - "message": "$PRODUCT_TYPE$ plans do not have access to real event logs", - "placeholders": { - "product_type": { - "content": "$1", - "example": "Teams" - } - } + "upgradeForFullEventsMessage": { + "message": "Event logs are not stored for your organization. Upgrade to a Teams or Enterprise plan to get full access to organization event logs." }, - "upgradeForFullEvents": { - "message": "Get full access to organization event logs by upgrading to a Teams or Enterprise plan." - }, - "upgradeEventLogTitle": { - "message": "Upgrade for real event log data" + "upgradeEventLogTitleMessage": { + "message": "Upgrade to see event logs from your organization." }, "upgradeEventLogMessage": { "message": "These events are examples only and do not reflect real events within your Bitwarden organization." @@ -10600,5 +10618,8 @@ }, "newBusinessUnit": { "message": "New business unit" + }, + "restart": { + "message": "Restart" } } diff --git a/apps/web/src/locales/sv/messages.json b/apps/web/src/locales/sv/messages.json index 5005b6180c7..b786bb5d322 100644 --- a/apps/web/src/locales/sv/messages.json +++ b/apps/web/src/locales/sv/messages.json @@ -2,6 +2,9 @@ "allApplications": { "message": "Alla applikationer" }, + "appLogoLabel": { + "message": "Bitwarden logo" + }, "criticalApplications": { "message": "Kritiska applikationer" }, @@ -419,18 +422,9 @@ "folder": { "message": "Mapp" }, - "newCustomField": { - "message": "Nytt anpassat fält" - }, "value": { "message": "Värde" }, - "dragToSort": { - "message": "Dra för att sortera" - }, - "dragToReorder": { - "message": "Drag to reorder" - }, "cfTypeText": { "message": "Text" }, @@ -4072,8 +4066,8 @@ "updateBrowser": { "message": "Uppdatera webbläsare" }, - "generatingRiskInsights": { - "message": "Generating your risk insights..." + "generatingYourRiskInsights": { + "message": "Generating your Risk Insights..." }, "updateBrowserDesc": { "message": "Du använder en webbläsare som inte stöds. Webbvalvet kanske inte fungerar som det ska." @@ -6305,6 +6299,21 @@ "sponsoredFamilies": { "message": "Free Bitwarden Families" }, + "sponsoredBitwardenFamilies": { + "message": "Sponsored families" + }, + "noSponsoredFamilies": { + "message": "No sponsored families" + }, + "noSponsoredFamiliesDescription": { + "message": "Sponsored non-member families plans will display here" + }, + "sponsorFreeBitwardenFamilies": { + "message": "Members of your organization are eligible for Free Bitwarden Families. You can sponsor Free Bitwarden Families for employees who are not a member of your Bitwarden organization. Sponsoring a non-member requires an available seat within your organization." + }, + "sponsoredFamiliesRemoveActiveSponsorship": { + "message": "When you remove an active sponsorship, a seat within your organization will be available after the renewal date of the sponsored organization." + }, "sponsoredFamiliesEligible": { "message": "You and your family are eligible for Free Bitwarden Families. Redeem with your personal email to keep your data secure even when you are not at work." }, @@ -6320,6 +6329,18 @@ "sponsoredFamiliesSharedCollections": { "message": "Shared collections for Family secrets" }, + "memberFamilies": { + "message": "Member families" + }, + "noMemberFamilies": { + "message": "No member families" + }, + "noMemberFamiliesDescription": { + "message": "Members who have redeemed family plans will display here" + }, + "membersWithSponsoredFamilies": { + "message": "Members of your organization are eligible for Free Bitwarden Families. Here you can see members who have sponsored a Families organization." + }, "badToken": { "message": "Länken är inte längre giltig. Be sponsorn att skicka erbjudandet igen." }, @@ -7249,6 +7270,9 @@ "followTheStepsBelowToFinishLoggingIn": { "message": "Follow the steps below to finish logging in." }, + "followTheStepsBelowToFinishLoggingInWithSecurityKey": { + "message": "Follow the steps below to finish logging in with your security key." + }, "launchDuo": { "message": "Starta Duo" }, @@ -7983,6 +8007,9 @@ "inviteMember": { "message": "Bjud in medlem" }, + "addSponsorship": { + "message": "Add sponsorship" + }, "needsConfirmation": { "message": "Kräver bekräftelse" }, @@ -10571,20 +10598,11 @@ "removeUnlockWithPinPolicyDesc": { "message": "Do not allow members to unlock their account with a PIN." }, - "limitedEventLogs": { - "message": "$PRODUCT_TYPE$ plans do not have access to real event logs", - "placeholders": { - "product_type": { - "content": "$1", - "example": "Teams" - } - } + "upgradeForFullEventsMessage": { + "message": "Event logs are not stored for your organization. Upgrade to a Teams or Enterprise plan to get full access to organization event logs." }, - "upgradeForFullEvents": { - "message": "Get full access to organization event logs by upgrading to a Teams or Enterprise plan." - }, - "upgradeEventLogTitle": { - "message": "Upgrade for real event log data" + "upgradeEventLogTitleMessage": { + "message": "Upgrade to see event logs from your organization." }, "upgradeEventLogMessage": { "message": "These events are examples only and do not reflect real events within your Bitwarden organization." @@ -10600,5 +10618,8 @@ }, "newBusinessUnit": { "message": "New business unit" + }, + "restart": { + "message": "Restart" } } diff --git a/apps/web/src/locales/te/messages.json b/apps/web/src/locales/te/messages.json index 293b3b0486f..6d8d0a9ca42 100644 --- a/apps/web/src/locales/te/messages.json +++ b/apps/web/src/locales/te/messages.json @@ -2,6 +2,9 @@ "allApplications": { "message": "All applications" }, + "appLogoLabel": { + "message": "Bitwarden logo" + }, "criticalApplications": { "message": "Critical applications" }, @@ -419,18 +422,9 @@ "folder": { "message": "Folder" }, - "newCustomField": { - "message": "New custom field" - }, "value": { "message": "Value" }, - "dragToSort": { - "message": "Drag to sort" - }, - "dragToReorder": { - "message": "Drag to reorder" - }, "cfTypeText": { "message": "Text" }, @@ -4072,8 +4066,8 @@ "updateBrowser": { "message": "Update browser" }, - "generatingRiskInsights": { - "message": "Generating your risk insights..." + "generatingYourRiskInsights": { + "message": "Generating your Risk Insights..." }, "updateBrowserDesc": { "message": "You are using an unsupported web browser. The web vault may not function properly." @@ -6305,6 +6299,21 @@ "sponsoredFamilies": { "message": "Free Bitwarden Families" }, + "sponsoredBitwardenFamilies": { + "message": "Sponsored families" + }, + "noSponsoredFamilies": { + "message": "No sponsored families" + }, + "noSponsoredFamiliesDescription": { + "message": "Sponsored non-member families plans will display here" + }, + "sponsorFreeBitwardenFamilies": { + "message": "Members of your organization are eligible for Free Bitwarden Families. You can sponsor Free Bitwarden Families for employees who are not a member of your Bitwarden organization. Sponsoring a non-member requires an available seat within your organization." + }, + "sponsoredFamiliesRemoveActiveSponsorship": { + "message": "When you remove an active sponsorship, a seat within your organization will be available after the renewal date of the sponsored organization." + }, "sponsoredFamiliesEligible": { "message": "You and your family are eligible for Free Bitwarden Families. Redeem with your personal email to keep your data secure even when you are not at work." }, @@ -6320,6 +6329,18 @@ "sponsoredFamiliesSharedCollections": { "message": "Shared collections for Family secrets" }, + "memberFamilies": { + "message": "Member families" + }, + "noMemberFamilies": { + "message": "No member families" + }, + "noMemberFamiliesDescription": { + "message": "Members who have redeemed family plans will display here" + }, + "membersWithSponsoredFamilies": { + "message": "Members of your organization are eligible for Free Bitwarden Families. Here you can see members who have sponsored a Families organization." + }, "badToken": { "message": "The link is no longer valid. Please have the sponsor resend the offer." }, @@ -7249,6 +7270,9 @@ "followTheStepsBelowToFinishLoggingIn": { "message": "Follow the steps below to finish logging in." }, + "followTheStepsBelowToFinishLoggingInWithSecurityKey": { + "message": "Follow the steps below to finish logging in with your security key." + }, "launchDuo": { "message": "Launch Duo" }, @@ -7983,6 +8007,9 @@ "inviteMember": { "message": "Invite member" }, + "addSponsorship": { + "message": "Add sponsorship" + }, "needsConfirmation": { "message": "Needs confirmation" }, @@ -10571,20 +10598,11 @@ "removeUnlockWithPinPolicyDesc": { "message": "Do not allow members to unlock their account with a PIN." }, - "limitedEventLogs": { - "message": "$PRODUCT_TYPE$ plans do not have access to real event logs", - "placeholders": { - "product_type": { - "content": "$1", - "example": "Teams" - } - } + "upgradeForFullEventsMessage": { + "message": "Event logs are not stored for your organization. Upgrade to a Teams or Enterprise plan to get full access to organization event logs." }, - "upgradeForFullEvents": { - "message": "Get full access to organization event logs by upgrading to a Teams or Enterprise plan." - }, - "upgradeEventLogTitle": { - "message": "Upgrade for real event log data" + "upgradeEventLogTitleMessage": { + "message": "Upgrade to see event logs from your organization." }, "upgradeEventLogMessage": { "message": "These events are examples only and do not reflect real events within your Bitwarden organization." @@ -10600,5 +10618,8 @@ }, "newBusinessUnit": { "message": "New business unit" + }, + "restart": { + "message": "Restart" } } diff --git a/apps/web/src/locales/th/messages.json b/apps/web/src/locales/th/messages.json index 7ea003470cb..9e4d01bba4b 100644 --- a/apps/web/src/locales/th/messages.json +++ b/apps/web/src/locales/th/messages.json @@ -2,6 +2,9 @@ "allApplications": { "message": "All applications" }, + "appLogoLabel": { + "message": "Bitwarden logo" + }, "criticalApplications": { "message": "Critical applications" }, @@ -419,18 +422,9 @@ "folder": { "message": "โฟลเดอร์" }, - "newCustomField": { - "message": "ฟิลด์ที่กำหนดเองใหม่" - }, "value": { "message": "ค่า" }, - "dragToSort": { - "message": "ลากเพื่อเรียงลำดับ" - }, - "dragToReorder": { - "message": "Drag to reorder" - }, "cfTypeText": { "message": "ข้อความ" }, @@ -4072,8 +4066,8 @@ "updateBrowser": { "message": "Update browser" }, - "generatingRiskInsights": { - "message": "Generating your risk insights..." + "generatingYourRiskInsights": { + "message": "Generating your Risk Insights..." }, "updateBrowserDesc": { "message": "You are using an unsupported web browser. The web vault may not function properly." @@ -6305,6 +6299,21 @@ "sponsoredFamilies": { "message": "Free Bitwarden Families" }, + "sponsoredBitwardenFamilies": { + "message": "Sponsored families" + }, + "noSponsoredFamilies": { + "message": "No sponsored families" + }, + "noSponsoredFamiliesDescription": { + "message": "Sponsored non-member families plans will display here" + }, + "sponsorFreeBitwardenFamilies": { + "message": "Members of your organization are eligible for Free Bitwarden Families. You can sponsor Free Bitwarden Families for employees who are not a member of your Bitwarden organization. Sponsoring a non-member requires an available seat within your organization." + }, + "sponsoredFamiliesRemoveActiveSponsorship": { + "message": "When you remove an active sponsorship, a seat within your organization will be available after the renewal date of the sponsored organization." + }, "sponsoredFamiliesEligible": { "message": "You and your family are eligible for Free Bitwarden Families. Redeem with your personal email to keep your data secure even when you are not at work." }, @@ -6320,6 +6329,18 @@ "sponsoredFamiliesSharedCollections": { "message": "Shared collections for Family secrets" }, + "memberFamilies": { + "message": "Member families" + }, + "noMemberFamilies": { + "message": "No member families" + }, + "noMemberFamiliesDescription": { + "message": "Members who have redeemed family plans will display here" + }, + "membersWithSponsoredFamilies": { + "message": "Members of your organization are eligible for Free Bitwarden Families. Here you can see members who have sponsored a Families organization." + }, "badToken": { "message": "The link is no longer valid. Please have the sponsor resend the offer." }, @@ -7249,6 +7270,9 @@ "followTheStepsBelowToFinishLoggingIn": { "message": "Follow the steps below to finish logging in." }, + "followTheStepsBelowToFinishLoggingInWithSecurityKey": { + "message": "Follow the steps below to finish logging in with your security key." + }, "launchDuo": { "message": "Launch Duo" }, @@ -7983,6 +8007,9 @@ "inviteMember": { "message": "Invite member" }, + "addSponsorship": { + "message": "Add sponsorship" + }, "needsConfirmation": { "message": "Needs confirmation" }, @@ -10571,20 +10598,11 @@ "removeUnlockWithPinPolicyDesc": { "message": "Do not allow members to unlock their account with a PIN." }, - "limitedEventLogs": { - "message": "$PRODUCT_TYPE$ plans do not have access to real event logs", - "placeholders": { - "product_type": { - "content": "$1", - "example": "Teams" - } - } + "upgradeForFullEventsMessage": { + "message": "Event logs are not stored for your organization. Upgrade to a Teams or Enterprise plan to get full access to organization event logs." }, - "upgradeForFullEvents": { - "message": "Get full access to organization event logs by upgrading to a Teams or Enterprise plan." - }, - "upgradeEventLogTitle": { - "message": "Upgrade for real event log data" + "upgradeEventLogTitleMessage": { + "message": "Upgrade to see event logs from your organization." }, "upgradeEventLogMessage": { "message": "These events are examples only and do not reflect real events within your Bitwarden organization." @@ -10600,5 +10618,8 @@ }, "newBusinessUnit": { "message": "New business unit" + }, + "restart": { + "message": "Restart" } } diff --git a/apps/web/src/locales/tr/messages.json b/apps/web/src/locales/tr/messages.json index da4b35b403e..fe392d73cf1 100644 --- a/apps/web/src/locales/tr/messages.json +++ b/apps/web/src/locales/tr/messages.json @@ -2,6 +2,9 @@ "allApplications": { "message": "Tüm uygulamalar" }, + "appLogoLabel": { + "message": "Bitwarden logosu" + }, "criticalApplications": { "message": "Kritik uygulamalar" }, @@ -419,18 +422,9 @@ "folder": { "message": "Klasör" }, - "newCustomField": { - "message": "Yeni özel alan" - }, "value": { "message": "Değer" }, - "dragToSort": { - "message": "Sıralamak için sürükleyin" - }, - "dragToReorder": { - "message": "Drag to reorder" - }, "cfTypeText": { "message": "Metin" }, @@ -4072,7 +4066,7 @@ "updateBrowser": { "message": "Tarayıcıyı güncelle" }, - "generatingRiskInsights": { + "generatingYourRiskInsights": { "message": "Risk içgörüleriniz oluşturuluyor..." }, "updateBrowserDesc": { @@ -6305,6 +6299,21 @@ "sponsoredFamilies": { "message": "Ücretsiz Bitwarden Aile" }, + "sponsoredBitwardenFamilies": { + "message": "Sponsored families" + }, + "noSponsoredFamilies": { + "message": "No sponsored families" + }, + "noSponsoredFamiliesDescription": { + "message": "Sponsored non-member families plans will display here" + }, + "sponsorFreeBitwardenFamilies": { + "message": "Members of your organization are eligible for Free Bitwarden Families. You can sponsor Free Bitwarden Families for employees who are not a member of your Bitwarden organization. Sponsoring a non-member requires an available seat within your organization." + }, + "sponsoredFamiliesRemoveActiveSponsorship": { + "message": "When you remove an active sponsorship, a seat within your organization will be available after the renewal date of the sponsored organization." + }, "sponsoredFamiliesEligible": { "message": "Siz ve aileniz Ücretsiz Bitwarden Aileleri için uygunsunuz. Verilerinizi işte olmadığınızda bile güvende tutmak için kişisel e-postanızla kullanın." }, @@ -6320,6 +6329,18 @@ "sponsoredFamiliesSharedCollections": { "message": "Aile sırları için paylaşılan koleksiyonlar" }, + "memberFamilies": { + "message": "Member families" + }, + "noMemberFamilies": { + "message": "No member families" + }, + "noMemberFamiliesDescription": { + "message": "Members who have redeemed family plans will display here" + }, + "membersWithSponsoredFamilies": { + "message": "Members of your organization are eligible for Free Bitwarden Families. Here you can see members who have sponsored a Families organization." + }, "badToken": { "message": "Bağlantı artık geçerli değil. Lütfen sponsorun yeniden teklif göndermesini isteyin." }, @@ -7249,6 +7270,9 @@ "followTheStepsBelowToFinishLoggingIn": { "message": "Follow the steps below to finish logging in." }, + "followTheStepsBelowToFinishLoggingInWithSecurityKey": { + "message": "Güvenlik anahtarınızla girişi tamamlamak için aşağıdaki adımları izleyin." + }, "launchDuo": { "message": "Duo'yu başlat" }, @@ -7983,6 +8007,9 @@ "inviteMember": { "message": "Üye davet et" }, + "addSponsorship": { + "message": "Add sponsorship" + }, "needsConfirmation": { "message": "Onay gerekli" }, @@ -10571,20 +10598,11 @@ "removeUnlockWithPinPolicyDesc": { "message": "Do not allow members to unlock their account with a PIN." }, - "limitedEventLogs": { - "message": "$PRODUCT_TYPE$ plans do not have access to real event logs", - "placeholders": { - "product_type": { - "content": "$1", - "example": "Teams" - } - } + "upgradeForFullEventsMessage": { + "message": "Event logs are not stored for your organization. Upgrade to a Teams or Enterprise plan to get full access to organization event logs." }, - "upgradeForFullEvents": { - "message": "Get full access to organization event logs by upgrading to a Teams or Enterprise plan." - }, - "upgradeEventLogTitle": { - "message": "Upgrade for real event log data" + "upgradeEventLogTitleMessage": { + "message": "Upgrade to see event logs from your organization." }, "upgradeEventLogMessage": { "message": "These events are examples only and do not reflect real events within your Bitwarden organization." @@ -10600,5 +10618,8 @@ }, "newBusinessUnit": { "message": "New business unit" + }, + "restart": { + "message": "Yeniden başlat" } } diff --git a/apps/web/src/locales/uk/messages.json b/apps/web/src/locales/uk/messages.json index a6bb37ed1d0..08532051ed0 100644 --- a/apps/web/src/locales/uk/messages.json +++ b/apps/web/src/locales/uk/messages.json @@ -2,6 +2,9 @@ "allApplications": { "message": "Всі програми" }, + "appLogoLabel": { + "message": "Логотип Bitwarden" + }, "criticalApplications": { "message": "Критичні програми" }, @@ -419,18 +422,9 @@ "folder": { "message": "Тека" }, - "newCustomField": { - "message": "Нове власне поле" - }, "value": { "message": "Значення" }, - "dragToSort": { - "message": "Перетягніть, щоб відсортувати" - }, - "dragToReorder": { - "message": "Потягніть, щоб упорядкувати" - }, "cfTypeText": { "message": "Текст" }, @@ -3324,10 +3318,10 @@ "message": "Зовнішній ID – це незашифроване посилання, призначене для використання Bitwarden Directory Connector і API." }, "ssoExternalId": { - "message": "SSO External ID" + "message": "Зовнішній ID SSO" }, "ssoExternalIdDesc": { - "message": "SSO External ID is an unencrypted reference between Bitwarden and your configured SSO provider." + "message": "Зовнішній ID SSO – це незашифроване посилання між Bitwarden і вашим налаштованим провайдером SSO." }, "nestCollectionUnder": { "message": "Розмістити збірку під" @@ -4072,7 +4066,7 @@ "updateBrowser": { "message": "Оновити браузер" }, - "generatingRiskInsights": { + "generatingYourRiskInsights": { "message": "Генерується інформація щодо ризиків..." }, "updateBrowserDesc": { @@ -5692,7 +5686,7 @@ "message": "WebAuthn успішно підтверджено! Ви можете закрити цю вкладку." }, "yourNewPasswordCannotBeTheSameAsYourCurrentPassword": { - "message": "Your new password cannot be the same as your current password." + "message": "Новий пароль повинен відрізнятися від поточного." }, "hintEqualsPassword": { "message": "Підказка повинна відрізнятися від пароля." @@ -5890,7 +5884,7 @@ "message": "Цифровий відбиток" }, "fingerprintPhrase": { - "message": "Fingerprint phrase:" + "message": "Фраза відбитка:" }, "removeUsers": { "message": "Вилучити користувачів" @@ -6305,6 +6299,21 @@ "sponsoredFamilies": { "message": "Bitwarden Families безплатно" }, + "sponsoredBitwardenFamilies": { + "message": "Спонсоровані родини" + }, + "noSponsoredFamilies": { + "message": "Немає спонсорованих родин" + }, + "noSponsoredFamiliesDescription": { + "message": "Тут буде показано спонсоровані плани родин, які не є учасниками" + }, + "sponsorFreeBitwardenFamilies": { + "message": "Учасники вашої організації мають право на безплатний план Bitwarden Families. Ви можете спонсорувати безплатні плани Bitwarden Families для співробітників, які не є учасниками вашої організації Bitwarden. Для спонсорування користувачів, які не є учасниками організації, необхідні вільні місця в межах вашої організації." + }, + "sponsoredFamiliesRemoveActiveSponsorship": { + "message": "Якщо вилучити активне спонсорство, місце у вашій організації стане доступним після дати оновлення спонсорованої організації." + }, "sponsoredFamiliesEligible": { "message": "Ви та ваша сім'я маєте право на Bitwarden Families безплатно. Активуйте доступ з особистою електронною адресою, щоб зберігати свої дані захищеними навіть коли ви не на роботі." }, @@ -6320,6 +6329,18 @@ "sponsoredFamiliesSharedCollections": { "message": "Спільні збірки для обміну паролями" }, + "memberFamilies": { + "message": "Родини учасників" + }, + "noMemberFamilies": { + "message": "Родини, які не є учасниками" + }, + "noMemberFamiliesDescription": { + "message": "Тут показуватимуться учасники, які скористалися родинним планом" + }, + "membersWithSponsoredFamilies": { + "message": "Учасники вашої організації мають право на безплатний план Bitwarden Families. Тут ви можете переглянути учасників, які спонсорують родинну організацію." + }, "badToken": { "message": "Посилання більше не дійсне. Попросіть спонсора повторно надіслати пропозицію." }, @@ -6730,7 +6751,7 @@ } }, "exportingIndividualVaultWithAttachmentsDescription": { - "message": "Only the individual vault items including attachments associated with $EMAIL$ will be exported. Organization vault items will not be included", + "message": "Будуть експортовані лише записи особистого сховища включно з вкладеннями, пов'язані з $EMAIL$. Записи сховища організації не експортуватимуться.", "placeholders": { "email": { "content": "$1", @@ -7249,6 +7270,9 @@ "followTheStepsBelowToFinishLoggingIn": { "message": "Виконайте наведені нижче кроки, щоб завершити вхід." }, + "followTheStepsBelowToFinishLoggingInWithSecurityKey": { + "message": "Виконайте наведені нижче кроки, щоб завершити вхід за допомогою ключа безпеки." + }, "launchDuo": { "message": "Запустити Duo" }, @@ -7983,6 +8007,9 @@ "inviteMember": { "message": "Запросити учасника" }, + "addSponsorship": { + "message": "Додати спонсорство" + }, "needsConfirmation": { "message": "Потребує підтвердження" }, @@ -8721,7 +8748,7 @@ "message": "Дозволити видалення збірок лише власникам та адміністраторам" }, "limitItemDeletionDescription": { - "message": "Limit item deletion to members with the Manage collection permissions" + "message": "Обмежити видалення записів для учасників, які мають дозвіл на керування збірками" }, "allowAdminAccessToAllCollectionItemsDesc": { "message": "Власники та адміністратори можуть керувати всіма збірками та записами" @@ -10343,31 +10370,31 @@ "message": "Назва організації не може перевищувати 50 символів." }, "rotationCompletedTitle": { - "message": "Key rotation successful" + "message": "Оновлення ключа пройшло успішно" }, "rotationCompletedDesc": { - "message": "Your master password and encryption keys have been updated. Your other devices have been logged out." + "message": "Ваш головний пароль і ключі шифрування оновлено. Сеанси на всіх інших пристроях завершено." }, "trustUserEmergencyAccess": { - "message": "Trust and confirm user" + "message": "Підтвердити й довіряти користувачу" }, "trustOrganization": { - "message": "Trust organization" + "message": "Довіряти організації" }, "trust": { - "message": "Trust" + "message": "Довіряти" }, "doNotTrust": { - "message": "Do not trust" + "message": "Не довіряти" }, "emergencyAccessTrustWarning": { - "message": "For the security of your account, only confirm if you have granted emergency access to this user and their fingerprint matches what is displayed in their account" + "message": "Щоб захистити свій обліковий запис, підтверджуйте лише якщо ви надали цьому користувачу екстрений доступ, і його цифровий відбиток збігається з показаним в його обліковому записі." }, "orgTrustWarning": { - "message": "For the security of your account, only proceed if you are a member of this organization, have account recovery enabled, and the fingerprint displayed below matches the organization's fingerprint." + "message": "Щоб захистити свій обліковий запис, продовжуйте лише якщо ви є учасником цієї організації, маєте ввімкнене відновлення облікового запису, і показаний нижче цифровий відбиток збігається з відбитком організації." }, "trustUser": { - "message": "Trust user" + "message": "Довіряти користувачу" }, "sshKeyWrongPassword": { "message": "Ви ввели неправильний пароль." @@ -10539,7 +10566,7 @@ "message": "Призначені місця перевищують доступні місця." }, "userkeyRotationDisclaimerEmergencyAccessText": { - "message": "Fingerprint phrase for $NUM_USERS$ contacts for which you have enabled emergency access.", + "message": "Фраза відбитка для контактів ($NUM_USERS$), яким ви активували екстрений доступ.", "placeholders": { "num_users": { "content": "$1", @@ -10548,7 +10575,7 @@ } }, "userkeyRotationDisclaimerAccountRecoveryOrgsText": { - "message": "Fingerprint phrase for the organization $ORG_NAME$ for which you have enabled account recovery.", + "message": "Фраза відбитка для організації $ORG_NAME$, якій ви активували відновлення облікового запису.", "placeholders": { "org_name": { "content": "$1", @@ -10557,10 +10584,10 @@ } }, "userkeyRotationDisclaimerDescription": { - "message": "Rotating your encryption keys will require you to trust keys of any organizations that can recover your account, and any contacts that you have enabled emergency access for. To continue, make sure you can verify the following:" + "message": "Оновлення ваших ключів шифрування потребуватиме довіри ключам будь-яких організацій, які можуть відновлювати ваш обліковий запис, а також будь-яких контактів, яким ви активували екстрений доступ. Щоб продовжити, переконайтеся, що ви можете перевірити зазначене нижче:" }, "userkeyRotationDisclaimerTitle": { - "message": "Untrusted encryption keys" + "message": "Недовірені ключі шифрування" }, "changeAtRiskPassword": { "message": "Змінити ризикований пароль" @@ -10571,20 +10598,11 @@ "removeUnlockWithPinPolicyDesc": { "message": "Не дозволяти учасникам розблокувати свій обліковий запис за допомогою PIN-коду." }, - "limitedEventLogs": { - "message": "Тарифні плани $PRODUCT_TYPE$ не мають доступу до реальних журналів подій", - "placeholders": { - "product_type": { - "content": "$1", - "example": "Teams" - } - } + "upgradeForFullEventsMessage": { + "message": "Event logs are not stored for your organization. Upgrade to a Teams or Enterprise plan to get full access to organization event logs." }, - "upgradeForFullEvents": { - "message": "Отримайте повний доступ до журналу подій організації, передплативши тарифний план Teams або Enterprise." - }, - "upgradeEventLogTitle": { - "message": "Оновіть тарифний план, щоб переглядати реальні дані журналу подій" + "upgradeEventLogTitleMessage": { + "message": "Upgrade to see event logs from your organization." }, "upgradeEventLogMessage": { "message": "Ці події є лише зразками. Вони не відображають реальних подій у вашій організації Bitwarden." @@ -10593,12 +10611,15 @@ "message": "Безплатні організації можуть мати до 2 збірок. Передплатіть тарифний план, щоб додати більше збірок." }, "businessUnit": { - "message": "Business Unit" + "message": "Бізнес-підрозділ" }, "businessUnits": { - "message": "Business Units" + "message": "Бізнес-підрозділи" }, "newBusinessUnit": { - "message": "New business unit" + "message": "Новий бізнес-підрозділ" + }, + "restart": { + "message": "Restart" } } diff --git a/apps/web/src/locales/vi/messages.json b/apps/web/src/locales/vi/messages.json index 0f2e31d834a..7cdf8059e69 100644 --- a/apps/web/src/locales/vi/messages.json +++ b/apps/web/src/locales/vi/messages.json @@ -2,6 +2,9 @@ "allApplications": { "message": "All applications" }, + "appLogoLabel": { + "message": "Bitwarden logo" + }, "criticalApplications": { "message": "Critical applications" }, @@ -419,18 +422,9 @@ "folder": { "message": "Thư mục" }, - "newCustomField": { - "message": "Trường tùy chỉnh mới" - }, "value": { "message": "Giá trị" }, - "dragToSort": { - "message": "Kéo để sắp xếp" - }, - "dragToReorder": { - "message": "Drag to reorder" - }, "cfTypeText": { "message": "Văn bản" }, @@ -4072,8 +4066,8 @@ "updateBrowser": { "message": "Update browser" }, - "generatingRiskInsights": { - "message": "Generating your risk insights..." + "generatingYourRiskInsights": { + "message": "Generating your Risk Insights..." }, "updateBrowserDesc": { "message": "You are using an unsupported web browser. The web vault may not function properly." @@ -6305,6 +6299,21 @@ "sponsoredFamilies": { "message": "Free Bitwarden Families" }, + "sponsoredBitwardenFamilies": { + "message": "Sponsored families" + }, + "noSponsoredFamilies": { + "message": "No sponsored families" + }, + "noSponsoredFamiliesDescription": { + "message": "Sponsored non-member families plans will display here" + }, + "sponsorFreeBitwardenFamilies": { + "message": "Members of your organization are eligible for Free Bitwarden Families. You can sponsor Free Bitwarden Families for employees who are not a member of your Bitwarden organization. Sponsoring a non-member requires an available seat within your organization." + }, + "sponsoredFamiliesRemoveActiveSponsorship": { + "message": "When you remove an active sponsorship, a seat within your organization will be available after the renewal date of the sponsored organization." + }, "sponsoredFamiliesEligible": { "message": "You and your family are eligible for Free Bitwarden Families. Redeem with your personal email to keep your data secure even when you are not at work." }, @@ -6320,6 +6329,18 @@ "sponsoredFamiliesSharedCollections": { "message": "Shared collections for Family secrets" }, + "memberFamilies": { + "message": "Member families" + }, + "noMemberFamilies": { + "message": "No member families" + }, + "noMemberFamiliesDescription": { + "message": "Members who have redeemed family plans will display here" + }, + "membersWithSponsoredFamilies": { + "message": "Members of your organization are eligible for Free Bitwarden Families. Here you can see members who have sponsored a Families organization." + }, "badToken": { "message": "The link is no longer valid. Please have the sponsor resend the offer." }, @@ -7249,6 +7270,9 @@ "followTheStepsBelowToFinishLoggingIn": { "message": "Follow the steps below to finish logging in." }, + "followTheStepsBelowToFinishLoggingInWithSecurityKey": { + "message": "Follow the steps below to finish logging in with your security key." + }, "launchDuo": { "message": "Launch Duo" }, @@ -7983,6 +8007,9 @@ "inviteMember": { "message": "Invite member" }, + "addSponsorship": { + "message": "Add sponsorship" + }, "needsConfirmation": { "message": "Needs confirmation" }, @@ -10571,20 +10598,11 @@ "removeUnlockWithPinPolicyDesc": { "message": "Do not allow members to unlock their account with a PIN." }, - "limitedEventLogs": { - "message": "$PRODUCT_TYPE$ plans do not have access to real event logs", - "placeholders": { - "product_type": { - "content": "$1", - "example": "Teams" - } - } + "upgradeForFullEventsMessage": { + "message": "Event logs are not stored for your organization. Upgrade to a Teams or Enterprise plan to get full access to organization event logs." }, - "upgradeForFullEvents": { - "message": "Get full access to organization event logs by upgrading to a Teams or Enterprise plan." - }, - "upgradeEventLogTitle": { - "message": "Upgrade for real event log data" + "upgradeEventLogTitleMessage": { + "message": "Upgrade to see event logs from your organization." }, "upgradeEventLogMessage": { "message": "These events are examples only and do not reflect real events within your Bitwarden organization." @@ -10600,5 +10618,8 @@ }, "newBusinessUnit": { "message": "New business unit" + }, + "restart": { + "message": "Restart" } } diff --git a/apps/web/src/locales/zh_CN/messages.json b/apps/web/src/locales/zh_CN/messages.json index b3196721a55..8797f3a2a91 100644 --- a/apps/web/src/locales/zh_CN/messages.json +++ b/apps/web/src/locales/zh_CN/messages.json @@ -2,6 +2,9 @@ "allApplications": { "message": "所有应用程序" }, + "appLogoLabel": { + "message": "Bitwarden logo" + }, "criticalApplications": { "message": "关键应用程序" }, @@ -408,10 +411,10 @@ "message": "无缝两步验证" }, "totpHelper": { - "message": "Bitwarden 可以存储并填充两步验证码。复制并粘贴密钥到此字段。" + "message": "Bitwarden 可以存储并填充两步验证码。将密钥复制并粘贴到此字段。" }, "totpHelperWithCapture": { - "message": "Bitwarden 可以存储并填充两步验证码。选择相机图标来截取此网站的验证器二维码,或者手动复制并粘贴密钥到此字段。" + "message": "Bitwarden 可以存储并填充两步验证码。选择相机图标来拍摄此网站的验证器二维码,或将密钥复制并粘贴到此字段。" }, "learnMoreAboutAuthenticators": { "message": "进一步了解验证器" @@ -419,18 +422,9 @@ "folder": { "message": "文件夹" }, - "newCustomField": { - "message": "新增自定义字段" - }, "value": { "message": "值" }, - "dragToSort": { - "message": "拖动排序" - }, - "dragToReorder": { - "message": "拖动以重新排序" - }, "cfTypeText": { "message": "文本型" }, @@ -2425,7 +2419,7 @@ "description": "Vault health reports can be used to evaluate the security of your Bitwarden individual or organization vault." }, "orgsReportsDesc": { - "message": "点击下面的报告来验证和消除您组织帐户中的安全漏洞。", + "message": "点击下面的报告,找出并消除您的组织账户中的安全漏洞。", "description": "Vault health reports can be used to evaluate the security of your Bitwarden individual or organization vault." }, "unsecuredWebsitesReport": { @@ -4072,8 +4066,8 @@ "updateBrowser": { "message": "更新浏览器" }, - "generatingRiskInsights": { - "message": "正在生成风险洞察..." + "generatingYourRiskInsights": { + "message": "Generating your Risk Insights..." }, "updateBrowserDesc": { "message": "您使用的是不受支持的网页浏览器。网页密码库可能无法正常运行。" @@ -4470,7 +4464,7 @@ } }, "encryptionKeyUpdateCannotProceed": { - "message": "无法继续加密密钥更新" + "message": "加密密钥更新无法继续" }, "editFieldLabel": { "message": "编辑 $LABEL$", @@ -6305,6 +6299,21 @@ "sponsoredFamilies": { "message": "免费 Bitwarden 家庭" }, + "sponsoredBitwardenFamilies": { + "message": "赞助的家庭" + }, + "noSponsoredFamilies": { + "message": "没有赞助的家庭" + }, + "noSponsoredFamiliesDescription": { + "message": "已赞助的非成员家庭计划将显示在这里" + }, + "sponsorFreeBitwardenFamilies": { + "message": "您的组织成员有资格获得免费的 Bitwarden 家庭版计划。您可以为不是您的 Bitwarden 组织成员的员工赞助免费 Bitwarden 家庭。赞助非成员需要您的组织内有可用的席位。" + }, + "sponsoredFamiliesRemoveActiveSponsorship": { + "message": "当您移除某个活动赞助时,该赞助席位将在被赞助组织的续费日期后释放给您的组织使用。" + }, "sponsoredFamiliesEligible": { "message": "您和您的家人有资格获得免费的 Bitwarden 家庭版计划。使用您的个人电子邮箱兑换,即使您不在工作中,也能确保您的数据安全。" }, @@ -6320,6 +6329,18 @@ "sponsoredFamiliesSharedCollections": { "message": "用于家庭机密的共享集合" }, + "memberFamilies": { + "message": "成员家庭" + }, + "noMemberFamilies": { + "message": "没有成员家庭" + }, + "noMemberFamiliesDescription": { + "message": "已兑换家庭计划的成员将在这里显示" + }, + "membersWithSponsoredFamilies": { + "message": "您的组织成员有资格获得免费的 Bitwarden 家庭版计划。在这里,您可以看到已赞助了家庭组织的成员。" + }, "badToken": { "message": "链接已失效。请让赞助方重新发送邀请。" }, @@ -6861,7 +6882,7 @@ "message": "使用此用户名" }, "securePasswordGenerated": { - "message": "安全密码生成好了!别忘了也在网站上更新一下您的密码。" + "message": "安全的密码生成好了!别忘了在网站上也更新一下您的密码。" }, "useGeneratorHelpTextPartOne": { "message": "使用生成器", @@ -7249,6 +7270,9 @@ "followTheStepsBelowToFinishLoggingIn": { "message": "按照以下步骤完成登录。" }, + "followTheStepsBelowToFinishLoggingInWithSecurityKey": { + "message": "请按照下面的步骤,使用您的安全密钥完成登录。" + }, "launchDuo": { "message": "启动 Duo" }, @@ -7340,7 +7364,7 @@ "description": "Description for the Projects field." }, "lastEdited": { - "message": "上次编辑", + "message": "最后编辑于", "description": "The label for the date and time when a item was last edited." }, "editSecret": { @@ -7983,6 +8007,9 @@ "inviteMember": { "message": "邀请成员" }, + "addSponsorship": { + "message": "添加赞助" + }, "needsConfirmation": { "message": "需要确认" }, @@ -10571,20 +10598,11 @@ "removeUnlockWithPinPolicyDesc": { "message": "不允许成员使用 PIN 码解锁他们的账户。" }, - "limitedEventLogs": { - "message": "$PRODUCT_TYPE$ 计划无法访问真实的事件日志", - "placeholders": { - "product_type": { - "content": "$1", - "example": "Teams" - } - } + "upgradeForFullEventsMessage": { + "message": "Event logs are not stored for your organization. Upgrade to a Teams or Enterprise plan to get full access to organization event logs." }, - "upgradeForFullEvents": { - "message": "升级到团队版或企业版计划,即可获得对组织事件日志的完整访问权限。" - }, - "upgradeEventLogTitle": { - "message": "升级以访问真实的事件日志数据" + "upgradeEventLogTitleMessage": { + "message": "Upgrade to see event logs from your organization." }, "upgradeEventLogMessage": { "message": "这些事件仅为示例,并不反映您 Bitwarden 组织内的真实事件。" @@ -10600,5 +10618,8 @@ }, "newBusinessUnit": { "message": "新增业务单元" + }, + "restart": { + "message": "Restart" } } diff --git a/apps/web/src/locales/zh_TW/messages.json b/apps/web/src/locales/zh_TW/messages.json index 23b932cd52a..b1cb44e7499 100644 --- a/apps/web/src/locales/zh_TW/messages.json +++ b/apps/web/src/locales/zh_TW/messages.json @@ -2,6 +2,9 @@ "allApplications": { "message": "所有應用程式" }, + "appLogoLabel": { + "message": "Bitwarden logo" + }, "criticalApplications": { "message": "重要應用程式" }, @@ -419,18 +422,9 @@ "folder": { "message": "資料夾" }, - "newCustomField": { - "message": "新增自訂欄位" - }, "value": { "message": "值" }, - "dragToSort": { - "message": "透過拖曳來排序" - }, - "dragToReorder": { - "message": "Drag to reorder" - }, "cfTypeText": { "message": "文字型" }, @@ -4072,8 +4066,8 @@ "updateBrowser": { "message": "更新瀏覽器" }, - "generatingRiskInsights": { - "message": "Generating your risk insights..." + "generatingYourRiskInsights": { + "message": "Generating your Risk Insights..." }, "updateBrowserDesc": { "message": "未支援您使用的瀏覽器。網頁版密碼庫可能無法正常運作。" @@ -6305,6 +6299,21 @@ "sponsoredFamilies": { "message": "免費的 Bitwarden 家庭方案" }, + "sponsoredBitwardenFamilies": { + "message": "Sponsored families" + }, + "noSponsoredFamilies": { + "message": "No sponsored families" + }, + "noSponsoredFamiliesDescription": { + "message": "Sponsored non-member families plans will display here" + }, + "sponsorFreeBitwardenFamilies": { + "message": "Members of your organization are eligible for Free Bitwarden Families. You can sponsor Free Bitwarden Families for employees who are not a member of your Bitwarden organization. Sponsoring a non-member requires an available seat within your organization." + }, + "sponsoredFamiliesRemoveActiveSponsorship": { + "message": "When you remove an active sponsorship, a seat within your organization will be available after the renewal date of the sponsored organization." + }, "sponsoredFamiliesEligible": { "message": "您與家庭成員可使用免費的 Bitwarden 家庭方案。就算不在上班時間,也可以使用您的私人電子郵件來兌換此方案,以保障您的資料安全。" }, @@ -6320,6 +6329,18 @@ "sponsoredFamiliesSharedCollections": { "message": "用於家庭機密的共用分類" }, + "memberFamilies": { + "message": "Member families" + }, + "noMemberFamilies": { + "message": "No member families" + }, + "noMemberFamiliesDescription": { + "message": "Members who have redeemed family plans will display here" + }, + "membersWithSponsoredFamilies": { + "message": "Members of your organization are eligible for Free Bitwarden Families. Here you can see members who have sponsored a Families organization." + }, "badToken": { "message": "連結已失效。請讓贊助者重新傳送邀請。" }, @@ -7249,6 +7270,9 @@ "followTheStepsBelowToFinishLoggingIn": { "message": "Follow the steps below to finish logging in." }, + "followTheStepsBelowToFinishLoggingInWithSecurityKey": { + "message": "Follow the steps below to finish logging in with your security key." + }, "launchDuo": { "message": "啟動 Duo" }, @@ -7983,6 +8007,9 @@ "inviteMember": { "message": "邀請成員" }, + "addSponsorship": { + "message": "Add sponsorship" + }, "needsConfirmation": { "message": "待確認" }, @@ -10571,20 +10598,11 @@ "removeUnlockWithPinPolicyDesc": { "message": "Do not allow members to unlock their account with a PIN." }, - "limitedEventLogs": { - "message": "$PRODUCT_TYPE$ plans do not have access to real event logs", - "placeholders": { - "product_type": { - "content": "$1", - "example": "Teams" - } - } + "upgradeForFullEventsMessage": { + "message": "Event logs are not stored for your organization. Upgrade to a Teams or Enterprise plan to get full access to organization event logs." }, - "upgradeForFullEvents": { - "message": "Get full access to organization event logs by upgrading to a Teams or Enterprise plan." - }, - "upgradeEventLogTitle": { - "message": "Upgrade for real event log data" + "upgradeEventLogTitleMessage": { + "message": "Upgrade to see event logs from your organization." }, "upgradeEventLogMessage": { "message": "These events are examples only and do not reflect real events within your Bitwarden organization." @@ -10600,5 +10618,8 @@ }, "newBusinessUnit": { "message": "New business unit" + }, + "restart": { + "message": "Restart" } } diff --git a/apps/web/src/scss/variables.scss b/apps/web/src/scss/variables.scss index b3a27a7824b..4b023e12746 100644 --- a/apps/web/src/scss/variables.scss +++ b/apps/web/src/scss/variables.scss @@ -20,8 +20,9 @@ $theme-colors: ( $body-bg: $white; $body-color: #333333; -$font-family-sans-serif: "DM Sans", "Helvetica Neue", Helvetica, Arial, sans-serif, - "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol"; +$font-family-sans-serif: + "DM Sans", "Helvetica Neue", Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", + "Segoe UI Symbol"; $h1-font-size: 1.7rem; $h2-font-size: 1.3rem; diff --git a/apps/web/webpack.config.js b/apps/web/webpack.config.js index 9ccccee21bf..a4ac3322200 100644 --- a/apps/web/webpack.config.js +++ b/apps/web/webpack.config.js @@ -122,7 +122,7 @@ const plugins = [ new HtmlWebpackPlugin({ template: "./src/connectors/sso.html", filename: "sso-connector.html", - chunks: ["connectors/sso"], + chunks: ["connectors/sso", "styles"], }), new HtmlWebpackPlugin({ template: "./src/connectors/redirect.html", diff --git a/bitwarden_license/bit-common/src/tools/reports/risk-insights/services/critical-apps.service.spec.ts b/bitwarden_license/bit-common/src/tools/reports/risk-insights/services/critical-apps.service.spec.ts index d2d48edf869..b3e8e11f4f7 100644 --- a/bitwarden_license/bit-common/src/tools/reports/risk-insights/services/critical-apps.service.spec.ts +++ b/bitwarden_license/bit-common/src/tools/reports/risk-insights/services/critical-apps.service.spec.ts @@ -59,7 +59,7 @@ describe("CriticalAppsService", () => { { id: "id2", organizationId: "org1", uri: "https://example.org" }, ] as PasswordHealthReportApplicationsResponse[]; - encryptService.encrypt.mockResolvedValue(new EncString("encryptedUrlName")); + encryptService.encryptString.mockResolvedValue(new EncString("encryptedUrlName")); criticalAppsApiService.saveCriticalApps.mockReturnValue(of(response)); // act @@ -67,7 +67,7 @@ describe("CriticalAppsService", () => { // expectations expect(keyService.getOrgKey).toHaveBeenCalledWith("org1"); - expect(encryptService.encrypt).toHaveBeenCalledTimes(2); + expect(encryptService.encryptString).toHaveBeenCalledTimes(2); expect(criticalAppsApiService.saveCriticalApps).toHaveBeenCalledWith(request); }); @@ -95,7 +95,7 @@ describe("CriticalAppsService", () => { { id: "id1", organizationId: "org1", uri: "test" }, ] as PasswordHealthReportApplicationsResponse[]; - encryptService.encrypt.mockResolvedValue(new EncString("encryptedUrlName")); + encryptService.encryptString.mockResolvedValue(new EncString("encryptedUrlName")); criticalAppsApiService.saveCriticalApps.mockReturnValue(of(response)); // act @@ -103,7 +103,7 @@ describe("CriticalAppsService", () => { // expectations expect(keyService.getOrgKey).toHaveBeenCalledWith("org1"); - expect(encryptService.encrypt).toHaveBeenCalledTimes(1); + expect(encryptService.encryptString).toHaveBeenCalledTimes(1); expect(criticalAppsApiService.saveCriticalApps).toHaveBeenCalledWith(request); }); @@ -114,7 +114,7 @@ describe("CriticalAppsService", () => { { id: "id2", organizationId: "org1", uri: "https://example.org" }, ] as PasswordHealthReportApplicationsResponse[]; - encryptService.decryptToUtf8.mockResolvedValue("https://example.com"); + encryptService.decryptString.mockResolvedValue("https://example.com"); criticalAppsApiService.getCriticalApps.mockReturnValue(of(response)); const mockRandomBytes = new Uint8Array(64) as CsprngArray; @@ -125,7 +125,7 @@ describe("CriticalAppsService", () => { flush(); expect(keyService.getOrgKey).toHaveBeenCalledWith(orgId.toString()); - expect(encryptService.decryptToUtf8).toHaveBeenCalledTimes(2); + expect(encryptService.decryptString).toHaveBeenCalledTimes(2); expect(criticalAppsApiService.getCriticalApps).toHaveBeenCalledWith(orgId); })); diff --git a/bitwarden_license/bit-common/src/tools/reports/risk-insights/services/critical-apps.service.ts b/bitwarden_license/bit-common/src/tools/reports/risk-insights/services/critical-apps.service.ts index bc8edc17360..b879ef94705 100644 --- a/bitwarden_license/bit-common/src/tools/reports/risk-insights/services/critical-apps.service.ts +++ b/bitwarden_license/bit-common/src/tools/reports/risk-insights/services/critical-apps.service.ts @@ -81,7 +81,7 @@ export class CriticalAppsService { // add the new entries to the criticalAppsList const updatedList = [...this.criticalAppsList.value]; for (const responseItem of dbResponse) { - const decryptedUrl = await this.encryptService.decryptToUtf8( + const decryptedUrl = await this.encryptService.decryptString( new EncString(responseItem.uri), key, ); @@ -138,7 +138,7 @@ export class CriticalAppsService { const results = response.map(async (r: PasswordHealthReportApplicationsResponse) => { const encrypted = new EncString(r.uri); - const uri = await this.encryptService.decryptToUtf8(encrypted, key); + const uri = await this.encryptService.decryptString(encrypted, key); return { id: r.id, organizationId: r.organizationId, uri: uri }; }); return forkJoin(results); @@ -164,7 +164,7 @@ export class CriticalAppsService { newEntries: string[], ): Promise { const criticalAppsPromises = newEntries.map(async (url) => { - const encryptedUrlName = await this.encryptService.encrypt(url, key); + const encryptedUrlName = await this.encryptService.encryptString(url, key); return { organizationId: orgId, url: encryptedUrlName?.encryptedString?.toString() ?? "", diff --git a/bitwarden_license/bit-web/src/app/admin-console/providers/manage/user-add-edit.component.html b/bitwarden_license/bit-web/src/app/admin-console/providers/manage/user-add-edit.component.html deleted file mode 100644 index 78d80d005c9..00000000000 --- a/bitwarden_license/bit-web/src/app/admin-console/providers/manage/user-add-edit.component.html +++ /dev/null @@ -1,124 +0,0 @@ - - - - - - {{ title }} - {{ name }} - - - × - - - - - {{ "loading" | i18n }} - - - - {{ "providerInviteUserDesc" | i18n }} - - {{ "email" | i18n }} - - {{ "inviteMultipleEmailDesc" | i18n: "20" }} - - - - {{ "userType" | i18n }} - - - - - - - - {{ "serviceUser" | i18n }} - {{ "serviceUserDesc" | i18n }} - - - - - - {{ "providerAdmin" | i18n }} - {{ "providerAdminDesc" | i18n }} - - - - - - - diff --git a/bitwarden_license/bit-web/src/app/admin-console/providers/manage/user-add-edit.component.ts b/bitwarden_license/bit-web/src/app/admin-console/providers/manage/user-add-edit.component.ts deleted file mode 100644 index 82671a7e418..00000000000 --- a/bitwarden_license/bit-web/src/app/admin-console/providers/manage/user-add-edit.component.ts +++ /dev/null @@ -1,125 +0,0 @@ -// FIXME: Update this file to be type safe and remove this and next line -// @ts-strict-ignore -import { Component, EventEmitter, Input, OnInit, Output } from "@angular/core"; - -import { ApiService } from "@bitwarden/common/abstractions/api.service"; -import { ProviderUserType } from "@bitwarden/common/admin-console/enums"; -import { PermissionsApi } from "@bitwarden/common/admin-console/models/api/permissions.api"; -import { ProviderUserInviteRequest } from "@bitwarden/common/admin-console/models/request/provider/provider-user-invite.request"; -import { ProviderUserUpdateRequest } from "@bitwarden/common/admin-console/models/request/provider/provider-user-update.request"; -import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; -import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; -import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; -import { DialogService, ToastService } from "@bitwarden/components"; - -/** - * @deprecated Please use the {@link MembersDialogComponent} instead. - */ -@Component({ - selector: "provider-user-add-edit", - templateUrl: "user-add-edit.component.html", -}) -export class UserAddEditComponent implements OnInit { - @Input() name: string; - @Input() providerUserId: string; - @Input() providerId: string; - @Output() savedUser = new EventEmitter(); - @Output() deletedUser = new EventEmitter(); - - loading = true; - editMode = false; - title: string; - emails: string; - type: ProviderUserType = ProviderUserType.ServiceUser; - permissions = new PermissionsApi(); - showCustom = false; - access: "all" | "selected" = "selected"; - formPromise: Promise; - deletePromise: Promise; - userType = ProviderUserType; - - constructor( - private apiService: ApiService, - private i18nService: I18nService, - private platformUtilsService: PlatformUtilsService, - private logService: LogService, - private dialogService: DialogService, - private toastService: ToastService, - ) {} - - async ngOnInit() { - this.editMode = this.loading = this.providerUserId != null; - - if (this.editMode) { - this.editMode = true; - this.title = this.i18nService.t("editMember"); - try { - const user = await this.apiService.getProviderUser(this.providerId, this.providerUserId); - this.type = user.type; - } catch (e) { - this.logService.error(e); - } - } else { - this.title = this.i18nService.t("inviteMember"); - } - - this.loading = false; - } - - async submit() { - try { - if (this.editMode) { - const request = new ProviderUserUpdateRequest(); - request.type = this.type; - this.formPromise = this.apiService.putProviderUser( - this.providerId, - this.providerUserId, - request, - ); - } else { - const request = new ProviderUserInviteRequest(); - request.emails = this.emails.trim().split(/\s*,\s*/); - request.type = this.type; - this.formPromise = this.apiService.postProviderUserInvite(this.providerId, request); - } - await this.formPromise; - this.toastService.showToast({ - variant: "success", - title: null, - message: this.i18nService.t(this.editMode ? "editedUserId" : "invitedUsers", this.name), - }); - this.savedUser.emit(); - } catch (e) { - this.logService.error(e); - } - } - - async delete() { - if (!this.editMode) { - return; - } - - const confirmed = await this.dialogService.openSimpleDialog({ - title: this.name, - content: { key: "removeUserConfirmation" }, - type: "warning", - }); - - if (!confirmed) { - return false; - } - - try { - this.deletePromise = this.apiService.deleteProviderUser(this.providerId, this.providerUserId); - await this.deletePromise; - this.toastService.showToast({ - variant: "success", - title: null, - message: this.i18nService.t("removedUserId", this.name), - }); - this.deletedUser.emit(); - } catch (e) { - this.logService.error(e); - } - } -} diff --git a/bitwarden_license/bit-web/src/app/admin-console/providers/providers.module.ts b/bitwarden_license/bit-web/src/app/admin-console/providers/providers.module.ts index dd9baa99948..597acb0d4f0 100644 --- a/bitwarden_license/bit-web/src/app/admin-console/providers/providers.module.ts +++ b/bitwarden_license/bit-web/src/app/admin-console/providers/providers.module.ts @@ -7,6 +7,7 @@ import { JslibModule } from "@bitwarden/angular/jslib.module"; import { CardComponent, SearchModule } from "@bitwarden/components"; import { DangerZoneComponent } from "@bitwarden/web-vault/app/auth/settings/account/danger-zone.component"; import { OrganizationPlansComponent } from "@bitwarden/web-vault/app/billing"; +import { PaymentComponent } from "@bitwarden/web-vault/app/billing/shared/payment/payment.component"; import { VerifyBankAccountComponent } from "@bitwarden/web-vault/app/billing/shared/verify-bank-account/verify-bank-account.component"; import { OssModule } from "@bitwarden/web-vault/app/oss.module"; @@ -29,7 +30,6 @@ import { BulkConfirmDialogComponent } from "./manage/dialogs/bulk-confirm-dialog import { BulkRemoveDialogComponent } from "./manage/dialogs/bulk-remove-dialog.component"; import { EventsComponent } from "./manage/events.component"; import { MembersComponent } from "./manage/members.component"; -import { UserAddEditComponent } from "./manage/user-add-edit.component"; import { ProvidersLayoutComponent } from "./providers-layout.component"; import { ProvidersRoutingModule } from "./providers-routing.module"; import { ProvidersComponent } from "./providers.component"; @@ -53,6 +53,7 @@ import { VerifyRecoverDeleteProviderComponent } from "./verify-recover-delete-pr ScrollingModule, VerifyBankAccountComponent, CardComponent, + PaymentComponent, ], declarations: [ AcceptProviderComponent, @@ -65,7 +66,6 @@ import { VerifyRecoverDeleteProviderComponent } from "./verify-recover-delete-pr MembersComponent, SetupComponent, SetupProviderComponent, - UserAddEditComponent, AddEditMemberDialogComponent, AddExistingOrganizationDialogComponent, CreateClientDialogComponent, diff --git a/bitwarden_license/bit-web/src/app/admin-console/providers/services/web-provider.service.ts b/bitwarden_license/bit-web/src/app/admin-console/providers/services/web-provider.service.ts index d3482ea67a5..844c6b779a9 100644 --- a/bitwarden_license/bit-web/src/app/admin-console/providers/services/web-provider.service.ts +++ b/bitwarden_license/bit-web/src/app/admin-console/providers/services/web-provider.service.ts @@ -36,7 +36,7 @@ export class WebProviderService { const orgKey = await this.keyService.getOrgKey(organizationId); const providerKey = await this.keyService.getProviderKey(providerId); - const encryptedOrgKey = await this.encryptService.encrypt(orgKey.key, providerKey); + const encryptedOrgKey = await this.encryptService.wrapSymmetricKey(orgKey, providerKey); const request = new ProviderAddOrganizationRequest(); request.organizationId = organizationId; @@ -55,7 +55,7 @@ export class WebProviderService { ), ); const providerKey = await this.keyService.getProviderKey(providerId); - const encryptedOrgKey = await this.encryptService.encrypt(orgKey.key, providerKey); + const encryptedOrgKey = await this.encryptService.wrapSymmetricKey(orgKey, providerKey); await this.providerApiService.addOrganizationToProvider(providerId, { key: encryptedOrgKey.encryptedString, organizationId, @@ -81,8 +81,8 @@ export class WebProviderService { const providerKey = await this.keyService.getProviderKey(providerId); - const encryptedProviderKey = await this.encryptService.encrypt( - organizationKey.key, + const encryptedProviderKey = await this.encryptService.wrapSymmetricKey( + organizationKey, providerKey, ); diff --git a/bitwarden_license/bit-web/src/app/admin-console/providers/setup/setup.component.html b/bitwarden_license/bit-web/src/app/admin-console/providers/setup/setup.component.html index 38b4c3bc9de..4c5a35ea58d 100644 --- a/bitwarden_license/bit-web/src/app/admin-console/providers/setup/setup.component.html +++ b/bitwarden_license/bit-web/src/app/admin-console/providers/setup/setup.component.html @@ -12,23 +12,50 @@ {{ "setupProviderDesc" | i18n }} - {{ "generalInformation" | i18n }} - - - - {{ "providerName" | i18n }} - - + @if (!(requireProviderPaymentMethodDuringSetup$ | async)) { + {{ "generalInformation" | i18n }} + + + + {{ "providerName" | i18n }} + + + + + + {{ "billingEmail" | i18n }} + + {{ "providerBillingEmailHint" | i18n }} + + - - - {{ "billingEmail" | i18n }} - - {{ "providerBillingEmailHint" | i18n }} - + + } @else { + {{ "billingInformation" | i18n }} + + + + {{ "providerName" | i18n }} + + + + + + {{ "billingEmail" | i18n }} + + {{ "providerBillingEmailHint" | i18n }} + + - - + {{ "paymentMethod" | i18n }} + + + } {{ "submit" | i18n }} diff --git a/bitwarden_license/bit-web/src/app/admin-console/providers/setup/setup.component.ts b/bitwarden_license/bit-web/src/app/admin-console/providers/setup/setup.component.ts index ecf649b8f31..0b6483b9f48 100644 --- a/bitwarden_license/bit-web/src/app/admin-console/providers/setup/setup.component.ts +++ b/bitwarden_license/bit-web/src/app/admin-console/providers/setup/setup.component.ts @@ -3,13 +3,14 @@ import { Component, OnDestroy, OnInit, ViewChild } from "@angular/core"; import { FormBuilder, Validators } from "@angular/forms"; import { ActivatedRoute, Router } from "@angular/router"; -import { Subject, switchMap } from "rxjs"; +import { firstValueFrom, Subject, switchMap } from "rxjs"; import { first, takeUntil } from "rxjs/operators"; import { ManageTaxInformationComponent } from "@bitwarden/angular/billing/components"; import { ProviderApiServiceAbstraction } from "@bitwarden/common/admin-console/abstractions/provider/provider-api.service.abstraction"; import { ProviderSetupRequest } from "@bitwarden/common/admin-console/models/request/provider/provider-setup.request"; import { ExpandedTaxInfoUpdateRequest } from "@bitwarden/common/billing/models/request/expanded-tax-info-update.request"; +import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum"; import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { ValidationService } from "@bitwarden/common/platform/abstractions/validation.service"; @@ -17,12 +18,14 @@ import { ProviderKey } from "@bitwarden/common/types/key"; import { SyncService } from "@bitwarden/common/vault/abstractions/sync/sync.service.abstraction"; import { ToastService } from "@bitwarden/components"; import { KeyService } from "@bitwarden/key-management"; +import { PaymentComponent } from "@bitwarden/web-vault/app/billing/shared/payment/payment.component"; @Component({ selector: "provider-setup", templateUrl: "setup.component.html", }) export class SetupComponent implements OnInit, OnDestroy { + @ViewChild(PaymentComponent) paymentComponent: PaymentComponent; @ViewChild(ManageTaxInformationComponent) taxInformationComponent: ManageTaxInformationComponent; loading = true; @@ -36,6 +39,10 @@ export class SetupComponent implements OnInit, OnDestroy { private destroy$ = new Subject(); + requireProviderPaymentMethodDuringSetup$ = this.configService.getFeatureFlag$( + FeatureFlag.PM19956_RequireProviderPaymentMethodDuringSetup, + ); + constructor( private router: Router, private i18nService: I18nService, @@ -134,6 +141,14 @@ export class SetupComponent implements OnInit, OnDestroy { request.taxInfo.city = taxInformation.city; request.taxInfo.state = taxInformation.state; + const requireProviderPaymentMethodDuringSetup = await firstValueFrom( + this.requireProviderPaymentMethodDuringSetup$, + ); + + if (requireProviderPaymentMethodDuringSetup) { + request.paymentSource = await this.paymentComponent.tokenize(); + } + const provider = await this.providerApiService.postProviderSetup(this.providerId, request); this.toastService.showToast({ diff --git a/bitwarden_license/bit-web/src/app/billing/providers/clients/manage-clients.component.html b/bitwarden_license/bit-web/src/app/billing/providers/clients/manage-clients.component.html index ffcda9fccfa..fa2e347e9c8 100644 --- a/bitwarden_license/bit-web/src/app/billing/providers/clients/manage-clients.component.html +++ b/bitwarden_license/bit-web/src/app/billing/providers/clients/manage-clients.component.html @@ -1,39 +1,25 @@ - - - - {{ "add" | i18n }} + + + {{ "add" | i18n }} + + + + + {{ newClientButtonLabel }} - - - - {{ newClientButtonLabel }} - - - - {{ "existingOrganization" | i18n }} - - - - - - - {{ "addNewOrganization" | i18n }} - - + + + {{ "existingOrganization" | i18n }} + + diff --git a/bitwarden_license/bit-web/src/app/billing/providers/clients/manage-clients.component.ts b/bitwarden_license/bit-web/src/app/billing/providers/clients/manage-clients.component.ts index 3322fd3a85b..a57e6351349 100644 --- a/bitwarden_license/bit-web/src/app/billing/providers/clients/manage-clients.component.ts +++ b/bitwarden_license/bit-web/src/app/billing/providers/clients/manage-clients.component.ts @@ -15,8 +15,6 @@ import { Provider } from "@bitwarden/common/admin-console/models/domain/provider import { ProviderOrganizationOrganizationDetailsResponse } from "@bitwarden/common/admin-console/models/response/provider/provider-organization.response"; import { BillingApiServiceAbstraction } from "@bitwarden/common/billing/abstractions"; import { PlanResponse } from "@bitwarden/common/billing/models/response/plan.response"; -import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum"; -import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { ValidationService } from "@bitwarden/common/platform/abstractions/validation.service"; import { @@ -73,9 +71,6 @@ export class ManageClientsComponent { protected searchControl = new FormControl("", { nonNullable: true }); protected plans: PlanResponse[] = []; - protected addExistingOrgsFromProviderPortal$ = this.configService.getFeatureFlag$( - FeatureFlag.PM15179_AddExistingOrgsFromProviderPortal, - ); pageTitle = this.i18nService.t("clients"); clientColumnHeader = this.i18nService.t("client"); @@ -91,7 +86,6 @@ export class ManageClientsComponent { private toastService: ToastService, private validationService: ValidationService, private webProviderService: WebProviderService, - private configService: ConfigService, private billingNotificationService: BillingNotificationService, ) { this.activatedRoute.queryParams.pipe(first(), takeUntilDestroyed()).subscribe((queryParams) => { diff --git a/bitwarden_license/bit-web/src/app/billing/providers/setup/setup-business-unit.component.ts b/bitwarden_license/bit-web/src/app/billing/providers/setup/setup-business-unit.component.ts index 4c8d483a0c5..f262ba1abd0 100644 --- a/bitwarden_license/bit-web/src/app/billing/providers/setup/setup-business-unit.component.ts +++ b/bitwarden_license/bit-web/src/app/billing/providers/setup/setup-business-unit.component.ts @@ -84,10 +84,8 @@ export class SetupBusinessUnitComponent extends BaseAcceptComponent { const organizationKey = await firstValueFrom(organizationKey$); - const { encryptedString: encryptedOrganizationKey } = await this.encryptService.encrypt( - organizationKey.key, - providerKey, - ); + const { encryptedString: encryptedOrganizationKey } = + await this.encryptService.wrapSymmetricKey(organizationKey, providerKey); if (!encryptedProviderKey || !encryptedOrganizationKey) { return await fail(); diff --git a/bitwarden_license/bit-web/src/app/secrets-manager/shared/access-policies/access-policy-selector/access-policy-selector.component.html b/bitwarden_license/bit-web/src/app/secrets-manager/shared/access-policies/access-policy-selector/access-policy-selector.component.html index 454b497fcdb..c8a50175781 100644 --- a/bitwarden_license/bit-web/src/app/secrets-manager/shared/access-policies/access-policy-selector/access-policy-selector.component.html +++ b/bitwarden_license/bit-web/src/app/secrets-manager/shared/access-policies/access-policy-selector/access-policy-selector.component.html @@ -1,4 +1,4 @@ - + {{ label }} @@ -97,7 +97,7 @@ {{ hint }} - + - + {{ "application" | i18n }} diff --git a/libs/angular/src/auth/components/change-password.component.ts b/libs/angular/src/auth/components/change-password.component.ts index 3b186a7fd2e..ca81f741b23 100644 --- a/libs/angular/src/auth/components/change-password.component.ts +++ b/libs/angular/src/auth/components/change-password.component.ts @@ -83,11 +83,12 @@ export class ChangePasswordComponent implements OnInit, OnDestroy { return; } - const email = await firstValueFrom( - this.accountService.activeAccount$.pipe(map((a) => a?.email)), + const [userId, email] = await firstValueFrom( + this.accountService.activeAccount$.pipe(map((a) => [a?.id, a?.email])), ); + if (this.kdfConfig == null) { - this.kdfConfig = await this.kdfConfigService.getKdfConfig(); + this.kdfConfig = await this.kdfConfigService.getKdfConfig(userId); } // Create new master key diff --git a/libs/angular/src/auth/components/set-password.component.ts b/libs/angular/src/auth/components/set-password.component.ts index ee0756355cf..230be90b7a4 100644 --- a/libs/angular/src/auth/components/set-password.component.ts +++ b/libs/angular/src/auth/components/set-password.component.ts @@ -178,7 +178,7 @@ export class SetPasswordComponent extends BaseChangePasswordComponent implements const existingUserPublicKeyB64 = Utils.fromBufferToB64(existingUserPublicKey); newKeyPair = [ existingUserPublicKeyB64, - await this.encryptService.encrypt(existingUserPrivateKey, userKey[0]), + await this.encryptService.wrapDecapsulationKey(existingUserPrivateKey, userKey[0]), ]; } else { newKeyPair = await this.keyService.makeKeyPair(userKey[0]); diff --git a/libs/angular/src/auth/components/update-password.component.ts b/libs/angular/src/auth/components/update-password.component.ts index 77e854753d7..47affbecdf2 100644 --- a/libs/angular/src/auth/components/update-password.component.ts +++ b/libs/angular/src/auth/components/update-password.component.ts @@ -2,6 +2,7 @@ // @ts-strict-ignore import { Directive } from "@angular/core"; import { Router } from "@angular/router"; +import { firstValueFrom } from "rxjs"; import { PolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction"; import { MasterPasswordPolicyOptions } from "@bitwarden/common/admin-console/models/domain/master-password-policy-options"; @@ -10,6 +11,7 @@ import { MasterPasswordApiService } from "@bitwarden/common/auth/abstractions/ma import { UserVerificationService } from "@bitwarden/common/auth/abstractions/user-verification/user-verification.service.abstraction"; import { VerificationType } from "@bitwarden/common/auth/enums/verification-type"; import { PasswordRequest } from "@bitwarden/common/auth/models/request/password.request"; +import { getUserId } from "@bitwarden/common/auth/services/account.service"; import { Verification } from "@bitwarden/common/auth/types/verification"; import { InternalMasterPasswordServiceAbstraction } from "@bitwarden/common/key-management/master-password/abstractions/master-password.service.abstraction"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; @@ -96,8 +98,8 @@ export class UpdatePasswordComponent extends BaseChangePasswordComponent { }); return false; } - - this.kdfConfig = await this.kdfConfigService.getKdfConfig(); + const userId = await firstValueFrom(getUserId(this.accountService.activeAccount$)); + this.kdfConfig = await this.kdfConfigService.getKdfConfig(userId); return true; } diff --git a/libs/angular/src/auth/components/update-temp-password.component.ts b/libs/angular/src/auth/components/update-temp-password.component.ts index 267beb2b822..db2f319998a 100644 --- a/libs/angular/src/auth/components/update-temp-password.component.ts +++ b/libs/angular/src/auth/components/update-temp-password.component.ts @@ -110,10 +110,11 @@ export class UpdateTempPasswordComponent extends BaseChangePasswordComponent imp } async setupSubmitActions(): Promise { - this.email = await firstValueFrom( - this.accountService.activeAccount$.pipe(map((a) => a?.email)), + const [userId, email] = await firstValueFrom( + this.accountService.activeAccount$.pipe(map((a) => [a?.id, a?.email])), ); - this.kdfConfig = await this.kdfConfigService.getKdfConfig(); + this.email = email; + this.kdfConfig = await this.kdfConfigService.getKdfConfig(userId); return true; } diff --git a/libs/angular/src/platform/abstractions/view-cache.service.ts b/libs/angular/src/platform/abstractions/view-cache.service.ts index a282ef67967..c5ae6c77d1f 100644 --- a/libs/angular/src/platform/abstractions/view-cache.service.ts +++ b/libs/angular/src/platform/abstractions/view-cache.service.ts @@ -18,6 +18,11 @@ type BaseCacheOptions = { /** An optional injector. Required if the method is called outside of an injection context. */ injector?: Injector; + + /** + * Optional flag to persist the cached value between navigation events. + */ + persistNavigation?: boolean; } & (T extends JsonValue ? Deserializer : Required>); export type SignalCacheOptions = BaseCacheOptions & { diff --git a/libs/angular/src/services/jslib-services.module.ts b/libs/angular/src/services/jslib-services.module.ts index 8e2b3409593..0d59f4a6547 100644 --- a/libs/angular/src/services/jslib-services.module.ts +++ b/libs/angular/src/services/jslib-services.module.ts @@ -136,11 +136,13 @@ import { import { AccountBillingApiServiceAbstraction } from "@bitwarden/common/billing/abstractions/account/account-billing-api.service.abstraction"; import { BillingAccountProfileStateService } from "@bitwarden/common/billing/abstractions/account/billing-account-profile-state.service"; import { OrganizationBillingApiServiceAbstraction } from "@bitwarden/common/billing/abstractions/organizations/organization-billing-api.service.abstraction"; +import { OrganizationSponsorshipApiServiceAbstraction } from "@bitwarden/common/billing/abstractions/organizations/organization-sponsorship-api.service.abstraction"; import { TaxServiceAbstraction } from "@bitwarden/common/billing/abstractions/tax.service.abstraction"; import { AccountBillingApiService } from "@bitwarden/common/billing/services/account/account-billing-api.service"; import { DefaultBillingAccountProfileStateService } from "@bitwarden/common/billing/services/account/billing-account-profile-state.service"; import { BillingApiService } from "@bitwarden/common/billing/services/billing-api.service"; import { OrganizationBillingApiService } from "@bitwarden/common/billing/services/organization/organization-billing-api.service"; +import { OrganizationSponsorshipApiService } from "@bitwarden/common/billing/services/organization/organization-sponsorship-api.service"; import { OrganizationBillingService } from "@bitwarden/common/billing/services/organization-billing.service"; import { TaxService } from "@bitwarden/common/billing/services/tax.service"; import { BulkEncryptService } from "@bitwarden/common/key-management/crypto/abstractions/bulk-encrypt.service"; @@ -270,6 +272,10 @@ import { } from "@bitwarden/common/vault/abstractions/folder/folder.service.abstraction"; import { TotpService as TotpServiceAbstraction } from "@bitwarden/common/vault/abstractions/totp.service"; import { VaultSettingsService as VaultSettingsServiceAbstraction } from "@bitwarden/common/vault/abstractions/vault-settings/vault-settings.service"; +import { + DefaultEndUserNotificationService, + EndUserNotificationService, +} from "@bitwarden/common/vault/notifications"; import { CipherAuthorizationService, DefaultCipherAuthorizationService, @@ -306,12 +312,7 @@ import { UserAsymmetricKeysRegenerationService, } from "@bitwarden/key-management"; import { SafeInjectionToken } from "@bitwarden/ui-common"; -import { - DefaultEndUserNotificationService, - EndUserNotificationService, - NewDeviceVerificationNoticeService, - PasswordRepromptService, -} from "@bitwarden/vault"; +import { NewDeviceVerificationNoticeService, PasswordRepromptService } from "@bitwarden/vault"; import { IndividualVaultExportService, IndividualVaultExportServiceAbstraction, @@ -1064,6 +1065,11 @@ const safeProviders: SafeProvider[] = [ // subscribes to sync notifications and will update itself based on that. deps: [ApiServiceAbstraction, SyncService], }), + safeProvider({ + provide: OrganizationSponsorshipApiServiceAbstraction, + useClass: OrganizationSponsorshipApiService, + deps: [ApiServiceAbstraction], + }), safeProvider({ provide: OrganizationBillingApiServiceAbstraction, useClass: OrganizationBillingApiService, @@ -1489,7 +1495,13 @@ const safeProviders: SafeProvider[] = [ safeProvider({ provide: EndUserNotificationService, useClass: DefaultEndUserNotificationService, - deps: [StateProvider, ApiServiceAbstraction, NotificationsService], + deps: [ + StateProvider, + ApiServiceAbstraction, + NotificationsService, + AuthServiceAbstraction, + LogService, + ], }), safeProvider({ provide: DeviceTrustToastServiceAbstraction, diff --git a/apps/web/src/app/vault/services/web-view-password-history.service.spec.ts b/libs/angular/src/services/view-password-history.service.spec.ts similarity index 69% rename from apps/web/src/app/vault/services/web-view-password-history.service.spec.ts rename to libs/angular/src/services/view-password-history.service.spec.ts index a4f73ed1a2e..dec2b25b190 100644 --- a/apps/web/src/app/vault/services/web-view-password-history.service.spec.ts +++ b/libs/angular/src/services/view-password-history.service.spec.ts @@ -3,17 +3,16 @@ import { TestBed } from "@angular/core/testing"; import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view"; import { DialogService } from "@bitwarden/components"; +import { openPasswordHistoryDialog } from "@bitwarden/vault"; -import { openPasswordHistoryDialog } from "../individual-vault/password-history.component"; +import { VaultViewPasswordHistoryService } from "./view-password-history.service"; -import { WebViewPasswordHistoryService } from "./web-view-password-history.service"; - -jest.mock("../individual-vault/password-history.component", () => ({ +jest.mock("@bitwarden/vault", () => ({ openPasswordHistoryDialog: jest.fn(), })); -describe("WebViewPasswordHistoryService", () => { - let service: WebViewPasswordHistoryService; +describe("VaultViewPasswordHistoryService", () => { + let service: VaultViewPasswordHistoryService; let dialogService: DialogService; beforeEach(async () => { @@ -23,13 +22,13 @@ describe("WebViewPasswordHistoryService", () => { await TestBed.configureTestingModule({ providers: [ - WebViewPasswordHistoryService, + VaultViewPasswordHistoryService, { provide: DialogService, useValue: mockDialogService }, Overlay, ], }).compileComponents(); - service = TestBed.inject(WebViewPasswordHistoryService); + service = TestBed.inject(VaultViewPasswordHistoryService); dialogService = TestBed.inject(DialogService); }); diff --git a/apps/web/src/app/vault/services/web-view-password-history.service.ts b/libs/angular/src/services/view-password-history.service.ts similarity index 78% rename from apps/web/src/app/vault/services/web-view-password-history.service.ts rename to libs/angular/src/services/view-password-history.service.ts index b1451b268de..88ca4d37287 100644 --- a/apps/web/src/app/vault/services/web-view-password-history.service.ts +++ b/libs/angular/src/services/view-password-history.service.ts @@ -3,14 +3,13 @@ import { Injectable } from "@angular/core"; import { ViewPasswordHistoryService } from "@bitwarden/common/vault/abstractions/view-password-history.service"; import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view"; import { DialogService } from "@bitwarden/components"; - -import { openPasswordHistoryDialog } from "../individual-vault/password-history.component"; +import { openPasswordHistoryDialog } from "@bitwarden/vault"; /** - * This service is used to display the password history dialog in the web vault. + * This service is used to display the password history dialog in the vault. */ @Injectable() -export class WebViewPasswordHistoryService implements ViewPasswordHistoryService { +export class VaultViewPasswordHistoryService implements ViewPasswordHistoryService { constructor(private dialogService: DialogService) {} /** diff --git a/libs/angular/src/vault/components/vault-items.component.ts b/libs/angular/src/vault/components/vault-items.component.ts index 852302cc0c4..c34816994be 100644 --- a/libs/angular/src/vault/components/vault-items.component.ts +++ b/libs/angular/src/vault/components/vault-items.component.ts @@ -18,6 +18,7 @@ import { Organization } from "@bitwarden/common/admin-console/models/domain/orga import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; import { getUserId } from "@bitwarden/common/auth/services/account.service"; import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service"; +import { CipherType } from "@bitwarden/common/vault/enums"; import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view"; @Directive() @@ -25,13 +26,14 @@ export class VaultItemsComponent implements OnInit, OnDestroy { @Input() activeCipherId: string = null; @Output() onCipherClicked = new EventEmitter(); @Output() onCipherRightClicked = new EventEmitter(); - @Output() onAddCipher = new EventEmitter(); + @Output() onAddCipher = new EventEmitter(); @Output() onAddCipherOptions = new EventEmitter(); loaded = false; ciphers: CipherView[] = []; deleted = false; organization: Organization; + CipherType = CipherType; protected searchPending = false; @@ -109,8 +111,8 @@ export class VaultItemsComponent implements OnInit, OnDestroy { this.onCipherRightClicked.emit(cipher); } - addCipher() { - this.onAddCipher.emit(); + addCipher(type?: CipherType) { + this.onAddCipher.emit(type); } addCipherOptions() { diff --git a/libs/auth/src/angular/login-decryption-options/login-decryption-options.component.ts b/libs/auth/src/angular/login-decryption-options/login-decryption-options.component.ts index 62fbeae26b6..945e6bbaaf5 100644 --- a/libs/auth/src/angular/login-decryption-options/login-decryption-options.component.ts +++ b/libs/auth/src/angular/login-decryption-options/login-decryption-options.component.ts @@ -284,7 +284,6 @@ export class LoginDecryptionOptionsComponent implements OnInit { } protected async approveFromOtherDevice() { - this.loginEmailService.setLoginEmail(this.email); await this.router.navigate(["/login-with-device"]); } @@ -297,7 +296,6 @@ export class LoginDecryptionOptionsComponent implements OnInit { } protected async requestAdminApproval() { - this.loginEmailService.setLoginEmail(this.email); await this.router.navigate(["/admin-approval-requested"]); } diff --git a/libs/auth/src/angular/login-via-auth-request/login-via-auth-request.component.ts b/libs/auth/src/angular/login-via-auth-request/login-via-auth-request.component.ts index 0af52e02b84..5de2339bda1 100644 --- a/libs/auth/src/angular/login-via-auth-request/login-via-auth-request.component.ts +++ b/libs/auth/src/angular/login-via-auth-request/login-via-auth-request.component.ts @@ -2,7 +2,7 @@ import { CommonModule } from "@angular/common"; import { Component, OnDestroy, OnInit } from "@angular/core"; import { takeUntilDestroyed } from "@angular/core/rxjs-interop"; import { IsActiveMatchOptions, Router, RouterModule } from "@angular/router"; -import { firstValueFrom, map } from "rxjs"; +import { Observable, filter, firstValueFrom, map, merge, race, take, timer } from "rxjs"; import { JslibModule } from "@bitwarden/angular/jslib.module"; import { @@ -178,7 +178,26 @@ export class LoginViaAuthRequestComponent implements OnInit, OnDestroy { private async initStandardAuthRequestFlow(): Promise { this.flow = Flow.StandardAuthRequest; - this.email = (await firstValueFrom(this.loginEmailService.loginEmail$)) || undefined; + // For a standard flow, we can get the user's email from two different places: + // 1. The loginEmailService, which is the email that the user is trying to log in with. This is cleared + // when the user logs in successfully. We can use this when the user is using Login with Device. + // 2. With TDE Login with Another Device, the user is already logged in and we just need to get + // a decryption key, so we can use the active account's email. + const activeAccountEmail$: Observable = + this.accountService.activeAccount$.pipe(map((a) => a?.email)); + const loginEmail$: Observable = this.loginEmailService.loginEmail$; + + // Use merge as we want to get the first value from either observable. + const firstEmail$ = merge(loginEmail$, activeAccountEmail$).pipe( + filter((e): e is string => !!e), // convert null/undefined to false and filter out so we narrow type to string + take(1), // complete after first value + ); + + const emailRetrievalTimeout$ = timer(2500).pipe(map(() => undefined as undefined)); + + // Wait for either the first email or the timeout to occur so we can proceed + // neither above observable will complete, so we have to add a timeout + this.email = await firstValueFrom(race(firstEmail$, emailRetrievalTimeout$)); if (!this.email) { await this.handleMissingEmail(); diff --git a/libs/auth/src/angular/login/login.component.ts b/libs/auth/src/angular/login/login.component.ts index 4ca18b4985e..eb2bdcee291 100644 --- a/libs/auth/src/angular/login/login.component.ts +++ b/libs/auth/src/angular/login/login.component.ts @@ -539,7 +539,7 @@ export class LoginComponent implements OnInit, OnDestroy { // If we load an email into the form, we need to initialize it for the login process as well // so that other login components can use it. // We do this here as it's possible that a user doesn't edit the email field before submitting. - this.loginEmailService.setLoginEmail(storedEmail); + await this.loginEmailService.setLoginEmail(storedEmail); } else { this.formGroup.controls.rememberEmail.setValue(false); } diff --git a/libs/auth/src/angular/password-hint/password-hint.component.ts b/libs/auth/src/angular/password-hint/password-hint.component.ts index 996b4d8d92e..cf24c68e10d 100644 --- a/libs/auth/src/angular/password-hint/password-hint.component.ts +++ b/libs/auth/src/angular/password-hint/password-hint.component.ts @@ -79,7 +79,7 @@ export class PasswordHintComponent implements OnInit { }; protected async cancel() { - this.loginEmailService.setLoginEmail(this.email); + await this.loginEmailService.setLoginEmail(this.email); await this.router.navigate(["login"]); } diff --git a/libs/auth/src/angular/two-factor-auth/child-components/two-factor-auth-authenticator.component.html b/libs/auth/src/angular/two-factor-auth/child-components/two-factor-auth-authenticator.component.html index af3a8569efa..b52af7b3820 100644 --- a/libs/auth/src/angular/two-factor-auth/child-components/two-factor-auth-authenticator.component.html +++ b/libs/auth/src/angular/two-factor-auth/child-components/two-factor-auth-authenticator.component.html @@ -1,6 +1,13 @@ {{ "verificationCode" | i18n }} - + diff --git a/libs/auth/src/angular/two-factor-auth/child-components/two-factor-auth-authenticator.component.ts b/libs/auth/src/angular/two-factor-auth/child-components/two-factor-auth-authenticator.component.ts index 333afc22d2a..d8735d3fd54 100644 --- a/libs/auth/src/angular/two-factor-auth/child-components/two-factor-auth-authenticator.component.ts +++ b/libs/auth/src/angular/two-factor-auth/child-components/two-factor-auth-authenticator.component.ts @@ -1,5 +1,5 @@ import { CommonModule } from "@angular/common"; -import { Component, Input } from "@angular/core"; +import { Component, Input, Output, EventEmitter } from "@angular/core"; import { ReactiveFormsModule, FormsModule, FormControl } from "@angular/forms"; import { JslibModule } from "@bitwarden/angular/jslib.module"; @@ -32,4 +32,10 @@ import { }) export class TwoFactorAuthAuthenticatorComponent { @Input({ required: true }) tokenFormControl: FormControl | undefined = undefined; + @Output() tokenChange = new EventEmitter<{ token: string }>(); + + onTokenChange(event: Event) { + const tokenValue = (event.target as HTMLInputElement).value || ""; + this.tokenChange.emit({ token: tokenValue }); + } } diff --git a/libs/auth/src/angular/two-factor-auth/child-components/two-factor-auth-email/two-factor-auth-component-email-cache.service.spec.ts b/libs/auth/src/angular/two-factor-auth/child-components/two-factor-auth-email/two-factor-auth-component-email-cache.service.spec.ts new file mode 100644 index 00000000000..f3b904a4ea6 --- /dev/null +++ b/libs/auth/src/angular/two-factor-auth/child-components/two-factor-auth-email/two-factor-auth-component-email-cache.service.spec.ts @@ -0,0 +1,165 @@ +import { TestBed } from "@angular/core/testing"; +import { mock, MockProxy } from "jest-mock-extended"; +import { BehaviorSubject } from "rxjs"; + +import { ViewCacheService } from "@bitwarden/angular/platform/abstractions/view-cache.service"; +import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum"; +import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; + +import { + TwoFactorAuthEmailComponentCache, + TwoFactorAuthEmailComponentCacheService, +} from "./two-factor-auth-email-component-cache.service"; + +describe("TwoFactorAuthEmailCache", () => { + describe("fromJSON", () => { + it("returns null when input is null", () => { + const result = TwoFactorAuthEmailComponentCache.fromJSON(null as any); + expect(result).toBeNull(); + }); + + it("creates a TwoFactorAuthEmailCache instance from valid JSON", () => { + const jsonData = { emailSent: true }; + const result = TwoFactorAuthEmailComponentCache.fromJSON(jsonData); + + expect(result).not.toBeNull(); + expect(result).toBeInstanceOf(TwoFactorAuthEmailComponentCache); + expect(result?.emailSent).toBe(true); + }); + }); +}); + +describe("TwoFactorAuthEmailComponentCacheService", () => { + let service: TwoFactorAuthEmailComponentCacheService; + let mockViewCacheService: MockProxy; + let mockConfigService: MockProxy; + let cacheData: BehaviorSubject; + let mockSignal: any; + + beforeEach(() => { + mockViewCacheService = mock(); + mockConfigService = mock(); + cacheData = new BehaviorSubject(null); + mockSignal = jest.fn(() => cacheData.getValue()); + mockSignal.set = jest.fn((value: TwoFactorAuthEmailComponentCache | null) => + cacheData.next(value), + ); + mockViewCacheService.signal.mockReturnValue(mockSignal); + + TestBed.configureTestingModule({ + providers: [ + TwoFactorAuthEmailComponentCacheService, + { provide: ViewCacheService, useValue: mockViewCacheService }, + { provide: ConfigService, useValue: mockConfigService }, + ], + }); + + service = TestBed.inject(TwoFactorAuthEmailComponentCacheService); + }); + + it("creates the service", () => { + expect(service).toBeTruthy(); + }); + + describe("init", () => { + it("sets featureEnabled to true when flag is enabled", async () => { + mockConfigService.getFeatureFlag.mockResolvedValue(true); + + await service.init(); + + expect(mockConfigService.getFeatureFlag).toHaveBeenCalledWith( + FeatureFlag.PM9115_TwoFactorExtensionDataPersistence, + ); + + service.cacheData({ emailSent: true }); + expect(mockSignal.set).toHaveBeenCalled(); + }); + + it("sets featureEnabled to false when flag is disabled", async () => { + mockConfigService.getFeatureFlag.mockResolvedValue(false); + + await service.init(); + + expect(mockConfigService.getFeatureFlag).toHaveBeenCalledWith( + FeatureFlag.PM9115_TwoFactorExtensionDataPersistence, + ); + + service.cacheData({ emailSent: true }); + expect(mockSignal.set).not.toHaveBeenCalled(); + }); + }); + + describe("cacheData", () => { + beforeEach(async () => { + mockConfigService.getFeatureFlag.mockResolvedValue(true); + await service.init(); + }); + + it("caches email sent state when feature is enabled", () => { + service.cacheData({ emailSent: true }); + + expect(mockSignal.set).toHaveBeenCalledWith({ + emailSent: true, + }); + }); + + it("does not cache data when feature is disabled", async () => { + mockConfigService.getFeatureFlag.mockResolvedValue(false); + await service.init(); + + service.cacheData({ emailSent: true }); + + expect(mockSignal.set).not.toHaveBeenCalled(); + }); + }); + + describe("clearCachedData", () => { + beforeEach(async () => { + mockConfigService.getFeatureFlag.mockResolvedValue(true); + await service.init(); + }); + + it("clears cached data when feature is enabled", () => { + service.clearCachedData(); + + expect(mockSignal.set).toHaveBeenCalledWith(null); + }); + + it("does not clear cached data when feature is disabled", async () => { + mockConfigService.getFeatureFlag.mockResolvedValue(false); + await service.init(); + + service.clearCachedData(); + + expect(mockSignal.set).not.toHaveBeenCalled(); + }); + }); + + describe("getCachedData", () => { + beforeEach(async () => { + mockConfigService.getFeatureFlag.mockResolvedValue(true); + await service.init(); + }); + + it("returns cached data when feature is enabled", () => { + const testData = new TwoFactorAuthEmailComponentCache(); + testData.emailSent = true; + cacheData.next(testData); + + const result = service.getCachedData(); + + expect(result).toEqual(testData); + expect(mockSignal).toHaveBeenCalled(); + }); + + it("returns null when feature is disabled", async () => { + mockConfigService.getFeatureFlag.mockResolvedValue(false); + await service.init(); + + const result = service.getCachedData(); + + expect(result).toBeNull(); + expect(mockSignal).not.toHaveBeenCalled(); + }); + }); +}); diff --git a/libs/auth/src/angular/two-factor-auth/child-components/two-factor-auth-email/two-factor-auth-email-component-cache.service.spec.ts b/libs/auth/src/angular/two-factor-auth/child-components/two-factor-auth-email/two-factor-auth-email-component-cache.service.spec.ts new file mode 100644 index 00000000000..1613c0e4af8 --- /dev/null +++ b/libs/auth/src/angular/two-factor-auth/child-components/two-factor-auth-email/two-factor-auth-email-component-cache.service.spec.ts @@ -0,0 +1,165 @@ +import { TestBed } from "@angular/core/testing"; +import { mock, MockProxy } from "jest-mock-extended"; +import { BehaviorSubject } from "rxjs"; + +import { ViewCacheService } from "@bitwarden/angular/platform/abstractions/view-cache.service"; +import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum"; +import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; + +import { + TwoFactorAuthEmailComponentCache, + TwoFactorAuthEmailComponentCacheService, +} from "./two-factor-auth-email-component-cache.service"; + +describe("TwoFactorAuthEmailComponentCache", () => { + describe("fromJSON", () => { + it("returns null when input is null", () => { + const result = TwoFactorAuthEmailComponentCache.fromJSON(null as any); + expect(result).toBeNull(); + }); + + it("creates a TwoFactorAuthEmailCache instance from valid JSON", () => { + const jsonData = { emailSent: true }; + const result = TwoFactorAuthEmailComponentCache.fromJSON(jsonData); + + expect(result).not.toBeNull(); + expect(result).toBeInstanceOf(TwoFactorAuthEmailComponentCache); + expect(result?.emailSent).toBe(true); + }); + }); +}); + +describe("TwoFactorAuthEmailComponentCacheService", () => { + let service: TwoFactorAuthEmailComponentCacheService; + let mockViewCacheService: MockProxy; + let mockConfigService: MockProxy; + let cacheData: BehaviorSubject; + let mockSignal: any; + + beforeEach(() => { + mockViewCacheService = mock(); + mockConfigService = mock(); + cacheData = new BehaviorSubject(null); + mockSignal = jest.fn(() => cacheData.getValue()); + mockSignal.set = jest.fn((value: TwoFactorAuthEmailComponentCache | null) => + cacheData.next(value), + ); + mockViewCacheService.signal.mockReturnValue(mockSignal); + + TestBed.configureTestingModule({ + providers: [ + TwoFactorAuthEmailComponentCacheService, + { provide: ViewCacheService, useValue: mockViewCacheService }, + { provide: ConfigService, useValue: mockConfigService }, + ], + }); + + service = TestBed.inject(TwoFactorAuthEmailComponentCacheService); + }); + + it("creates the service", () => { + expect(service).toBeTruthy(); + }); + + describe("init", () => { + it("sets featureEnabled to true when flag is enabled", async () => { + mockConfigService.getFeatureFlag.mockResolvedValue(true); + + await service.init(); + + expect(mockConfigService.getFeatureFlag).toHaveBeenCalledWith( + FeatureFlag.PM9115_TwoFactorExtensionDataPersistence, + ); + + service.cacheData({ emailSent: true }); + expect(mockSignal.set).toHaveBeenCalled(); + }); + + it("sets featureEnabled to false when flag is disabled", async () => { + mockConfigService.getFeatureFlag.mockResolvedValue(false); + + await service.init(); + + expect(mockConfigService.getFeatureFlag).toHaveBeenCalledWith( + FeatureFlag.PM9115_TwoFactorExtensionDataPersistence, + ); + + service.cacheData({ emailSent: true }); + expect(mockSignal.set).not.toHaveBeenCalled(); + }); + }); + + describe("cacheData", () => { + beforeEach(async () => { + mockConfigService.getFeatureFlag.mockResolvedValue(true); + await service.init(); + }); + + it("caches email sent state when feature is enabled", () => { + service.cacheData({ emailSent: true }); + + expect(mockSignal.set).toHaveBeenCalledWith({ + emailSent: true, + }); + }); + + it("does not cache data when feature is disabled", async () => { + mockConfigService.getFeatureFlag.mockResolvedValue(false); + await service.init(); + + service.cacheData({ emailSent: true }); + + expect(mockSignal.set).not.toHaveBeenCalled(); + }); + }); + + describe("clearCachedData", () => { + beforeEach(async () => { + mockConfigService.getFeatureFlag.mockResolvedValue(true); + await service.init(); + }); + + it("clears cached data when feature is enabled", () => { + service.clearCachedData(); + + expect(mockSignal.set).toHaveBeenCalledWith(null); + }); + + it("does not clear cached data when feature is disabled", async () => { + mockConfigService.getFeatureFlag.mockResolvedValue(false); + await service.init(); + + service.clearCachedData(); + + expect(mockSignal.set).not.toHaveBeenCalled(); + }); + }); + + describe("getCachedData", () => { + beforeEach(async () => { + mockConfigService.getFeatureFlag.mockResolvedValue(true); + await service.init(); + }); + + it("returns cached data when feature is enabled", () => { + const testData = new TwoFactorAuthEmailComponentCache(); + testData.emailSent = true; + cacheData.next(testData); + + const result = service.getCachedData(); + + expect(result).toEqual(testData); + expect(mockSignal).toHaveBeenCalled(); + }); + + it("returns null when feature is disabled", async () => { + mockConfigService.getFeatureFlag.mockResolvedValue(false); + await service.init(); + + const result = service.getCachedData(); + + expect(result).toBeNull(); + expect(mockSignal).not.toHaveBeenCalled(); + }); + }); +}); diff --git a/libs/auth/src/angular/two-factor-auth/child-components/two-factor-auth-email/two-factor-auth-email-component-cache.service.ts b/libs/auth/src/angular/two-factor-auth/child-components/two-factor-auth-email/two-factor-auth-email-component-cache.service.ts new file mode 100644 index 00000000000..e32b6cd1385 --- /dev/null +++ b/libs/auth/src/angular/two-factor-auth/child-components/two-factor-auth-email/two-factor-auth-email-component-cache.service.ts @@ -0,0 +1,95 @@ +import { inject, Injectable, WritableSignal } from "@angular/core"; +import { Jsonify } from "type-fest"; + +import { ViewCacheService } from "@bitwarden/angular/platform/abstractions/view-cache.service"; +import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum"; +import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; + +/** + * The key for the email two factor auth component cache. + */ +export const TWO_FACTOR_AUTH_EMAIL_COMPONENT_CACHE_KEY = "two-factor-auth-email-component-cache"; + +/** + * Cache model for the email two factor auth component. + */ +export class TwoFactorAuthEmailComponentCache { + emailSent: boolean = false; + + static fromJSON( + obj: Partial>, + ): TwoFactorAuthEmailComponentCache | null { + // Return null if the cache is empty + if (obj == null) { + return null; + } + + return Object.assign(new TwoFactorAuthEmailComponentCache(), obj); + } +} + +/** + * Cache service for the two factor auth email component. + */ +@Injectable() +export class TwoFactorAuthEmailComponentCacheService { + private viewCacheService: ViewCacheService = inject(ViewCacheService); + private configService: ConfigService = inject(ConfigService); + + /** True when the feature flag is enabled */ + private featureEnabled: boolean = false; + + /** + * Signal for the cached email state. + */ + private emailCache: WritableSignal = + this.viewCacheService.signal({ + key: TWO_FACTOR_AUTH_EMAIL_COMPONENT_CACHE_KEY, + initialValue: null, + deserializer: TwoFactorAuthEmailComponentCache.fromJSON, + }); + + /** + * Must be called once before interacting with the cached data. + */ + async init() { + this.featureEnabled = await this.configService.getFeatureFlag( + FeatureFlag.PM9115_TwoFactorExtensionDataPersistence, + ); + } + + /** + * Cache the email sent state. + */ + cacheData(data: { emailSent: boolean }): void { + if (!this.featureEnabled) { + return; + } + + this.emailCache.set({ + emailSent: data.emailSent, + } as TwoFactorAuthEmailComponentCache); + } + + /** + * Clear the cached email data. + */ + clearCachedData(): void { + if (!this.featureEnabled) { + return; + } + + this.emailCache.set(null); + } + + /** + * Get whether the email has been sent. + */ + getCachedData(): TwoFactorAuthEmailComponentCache | null { + if (!this.featureEnabled) { + return null; + } + + return this.emailCache(); + } +} diff --git a/libs/auth/src/angular/two-factor-auth/child-components/two-factor-auth-email/two-factor-auth-email.component.html b/libs/auth/src/angular/two-factor-auth/child-components/two-factor-auth-email/two-factor-auth-email.component.html index 41873c32ed0..90f1d74ae48 100644 --- a/libs/auth/src/angular/two-factor-auth/child-components/two-factor-auth-email/two-factor-auth-email.component.html +++ b/libs/auth/src/angular/two-factor-auth/child-components/two-factor-auth-email/two-factor-auth-email.component.html @@ -1,6 +1,13 @@ {{ "verificationCode" | i18n }} - + diff --git a/libs/auth/src/angular/two-factor-auth/child-components/two-factor-auth-email/two-factor-auth-email.component.ts b/libs/auth/src/angular/two-factor-auth/child-components/two-factor-auth-email/two-factor-auth-email.component.ts index 0c67416532f..25235017bd1 100644 --- a/libs/auth/src/angular/two-factor-auth/child-components/two-factor-auth-email/two-factor-auth-email.component.ts +++ b/libs/auth/src/angular/two-factor-auth/child-components/two-factor-auth-email/two-factor-auth-email.component.ts @@ -1,5 +1,5 @@ import { CommonModule } from "@angular/common"; -import { Component, Input, OnInit } from "@angular/core"; +import { Component, Input, OnInit, Output, EventEmitter } from "@angular/core"; import { ReactiveFormsModule, FormsModule, FormControl } from "@angular/forms"; import { JslibModule } from "@bitwarden/angular/jslib.module"; @@ -22,6 +22,7 @@ import { ToastService, } from "@bitwarden/components"; +import { TwoFactorAuthEmailComponentCacheService } from "./two-factor-auth-email-component-cache.service"; import { TwoFactorAuthEmailComponentService } from "./two-factor-auth-email-component.service"; @Component({ @@ -40,14 +41,20 @@ import { TwoFactorAuthEmailComponentService } from "./two-factor-auth-email-comp AsyncActionsModule, FormsModule, ], - providers: [], + providers: [ + { + provide: TwoFactorAuthEmailComponentCacheService, + useClass: TwoFactorAuthEmailComponentCacheService, + }, + ], }) export class TwoFactorAuthEmailComponent implements OnInit { @Input({ required: true }) tokenFormControl: FormControl | undefined = undefined; + @Output() tokenChange = new EventEmitter<{ token: string }>(); twoFactorEmail: string | undefined = undefined; - emailPromise: Promise | undefined = undefined; - tokenValue: string = ""; + emailPromise: Promise | undefined; + emailSent = false; constructor( protected i18nService: I18nService, @@ -59,14 +66,22 @@ export class TwoFactorAuthEmailComponent implements OnInit { protected appIdService: AppIdService, private toastService: ToastService, private twoFactorAuthEmailComponentService: TwoFactorAuthEmailComponentService, + private cacheService: TwoFactorAuthEmailComponentCacheService, ) {} async ngOnInit(): Promise { await this.twoFactorAuthEmailComponentService.openPopoutIfApprovedForEmail2fa?.(); + await this.cacheService.init(); + + // Check if email was already sent + const cachedData = this.cacheService.getCachedData(); + if (cachedData?.emailSent) { + this.emailSent = true; + } const providers = await this.twoFactorService.getProviders(); - if (!providers) { + if (!providers || providers.size === 0) { throw new Error("User has no 2FA Providers"); } @@ -78,11 +93,20 @@ export class TwoFactorAuthEmailComponent implements OnInit { this.twoFactorEmail = email2faProviderData.Email; - if (providers.size > 1) { + if (!this.emailSent) { await this.sendEmail(false); } } + /** + * Emits the token value to the parent component + * @param event - The event object from the input field + */ + onTokenChange(event: Event) { + const tokenValue = (event.target as HTMLInputElement).value || ""; + this.tokenChange.emit({ token: tokenValue }); + } + async sendEmail(doToast: boolean) { if (this.emailPromise !== undefined) { return; @@ -113,6 +137,10 @@ export class TwoFactorAuthEmailComponent implements OnInit { request.authRequestId = (await this.loginStrategyService.getAuthRequestId()) ?? ""; this.emailPromise = this.apiService.postTwoFactorEmail(request); await this.emailPromise; + + this.emailSent = true; + this.cacheService.cacheData({ emailSent: this.emailSent }); + if (doToast) { this.toastService.showToast({ variant: "success", diff --git a/libs/auth/src/angular/two-factor-auth/two-factor-auth-component-cache.service.spec.ts b/libs/auth/src/angular/two-factor-auth/two-factor-auth-component-cache.service.spec.ts new file mode 100644 index 00000000000..0993954fde1 --- /dev/null +++ b/libs/auth/src/angular/two-factor-auth/two-factor-auth-component-cache.service.spec.ts @@ -0,0 +1,191 @@ +import { TestBed } from "@angular/core/testing"; +import { mock, MockProxy } from "jest-mock-extended"; +import { BehaviorSubject } from "rxjs"; + +import { ViewCacheService } from "@bitwarden/angular/platform/abstractions/view-cache.service"; +import { TwoFactorProviderType } from "@bitwarden/common/auth/enums/two-factor-provider-type"; +import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum"; +import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; + +import { + TwoFactorAuthComponentCache, + TwoFactorAuthComponentCacheService, + TwoFactorAuthComponentData, +} from "./two-factor-auth-component-cache.service"; + +describe("TwoFactorAuthCache", () => { + describe("fromJSON", () => { + it("returns null when input is null", () => { + const result = TwoFactorAuthComponentCache.fromJSON(null as any); + expect(result).toBeNull(); + }); + + it("creates a TwoFactorAuthCache instance from valid JSON", () => { + const jsonData = { + token: "123456", + remember: true, + selectedProviderType: TwoFactorProviderType.Email, + }; + const result = TwoFactorAuthComponentCache.fromJSON(jsonData as any); + + expect(result).not.toBeNull(); + expect(result).toBeInstanceOf(TwoFactorAuthComponentCache); + expect(result?.token).toBe("123456"); + expect(result?.remember).toBe(true); + expect(result?.selectedProviderType).toBe(TwoFactorProviderType.Email); + }); + }); +}); + +describe("TwoFactorAuthComponentCacheService", () => { + let service: TwoFactorAuthComponentCacheService; + let mockViewCacheService: MockProxy; + let mockConfigService: MockProxy; + let cacheData: BehaviorSubject; + let mockSignal: any; + + beforeEach(() => { + mockViewCacheService = mock(); + mockConfigService = mock(); + cacheData = new BehaviorSubject(null); + mockSignal = jest.fn(() => cacheData.getValue()); + mockSignal.set = jest.fn((value: TwoFactorAuthComponentCache | null) => cacheData.next(value)); + mockViewCacheService.signal.mockReturnValue(mockSignal); + + TestBed.configureTestingModule({ + providers: [ + TwoFactorAuthComponentCacheService, + { provide: ViewCacheService, useValue: mockViewCacheService }, + { provide: ConfigService, useValue: mockConfigService }, + ], + }); + + service = TestBed.inject(TwoFactorAuthComponentCacheService); + }); + + it("creates the service", () => { + expect(service).toBeTruthy(); + }); + + describe("init", () => { + it("sets featureEnabled to true when flag is enabled", async () => { + mockConfigService.getFeatureFlag.mockResolvedValue(true); + + await service.init(); + + expect(mockConfigService.getFeatureFlag).toHaveBeenCalledWith( + FeatureFlag.PM9115_TwoFactorExtensionDataPersistence, + ); + + service.cacheData({ token: "123456" }); + expect(mockSignal.set).toHaveBeenCalled(); + }); + + it("sets featureEnabled to false when flag is disabled", async () => { + mockConfigService.getFeatureFlag.mockResolvedValue(false); + + await service.init(); + + expect(mockConfigService.getFeatureFlag).toHaveBeenCalledWith( + FeatureFlag.PM9115_TwoFactorExtensionDataPersistence, + ); + + service.cacheData({ token: "123456" }); + expect(mockSignal.set).not.toHaveBeenCalled(); + }); + }); + + describe("cacheData", () => { + beforeEach(async () => { + mockConfigService.getFeatureFlag.mockResolvedValue(true); + await service.init(); + }); + + it("caches complete data when feature is enabled", () => { + const testData: TwoFactorAuthComponentData = { + token: "123456", + remember: true, + selectedProviderType: TwoFactorProviderType.Email, + }; + + service.cacheData(testData); + + expect(mockSignal.set).toHaveBeenCalledWith({ + token: "123456", + remember: true, + selectedProviderType: TwoFactorProviderType.Email, + }); + }); + + it("caches partial data when feature is enabled", () => { + service.cacheData({ token: "123456" }); + + expect(mockSignal.set).toHaveBeenCalledWith({ + token: "123456", + remember: undefined, + selectedProviderType: undefined, + }); + }); + + it("does not cache data when feature is disabled", async () => { + mockConfigService.getFeatureFlag.mockResolvedValue(false); + await service.init(); + + service.cacheData({ token: "123456" }); + + expect(mockSignal.set).not.toHaveBeenCalled(); + }); + }); + + describe("clearCachedData", () => { + beforeEach(async () => { + mockConfigService.getFeatureFlag.mockResolvedValue(true); + await service.init(); + }); + + it("clears cached data when feature is enabled", () => { + service.clearCachedData(); + + expect(mockSignal.set).toHaveBeenCalledWith(null); + }); + + it("does not clear cached data when feature is disabled", async () => { + mockConfigService.getFeatureFlag.mockResolvedValue(false); + await service.init(); + + service.clearCachedData(); + + expect(mockSignal.set).not.toHaveBeenCalled(); + }); + }); + + describe("getCachedData", () => { + beforeEach(async () => { + mockConfigService.getFeatureFlag.mockResolvedValue(true); + await service.init(); + }); + + it("returns cached data when feature is enabled", () => { + const testData = new TwoFactorAuthComponentCache(); + testData.token = "123456"; + testData.remember = true; + testData.selectedProviderType = TwoFactorProviderType.Email; + cacheData.next(testData); + + const result = service.getCachedData(); + + expect(result).toEqual(testData); + expect(mockSignal).toHaveBeenCalled(); + }); + + it("returns null when feature is disabled", async () => { + mockConfigService.getFeatureFlag.mockResolvedValue(false); + await service.init(); + + const result = service.getCachedData(); + + expect(result).toBeNull(); + expect(mockSignal).not.toHaveBeenCalled(); + }); + }); +}); diff --git a/libs/auth/src/angular/two-factor-auth/two-factor-auth-component-cache.service.ts b/libs/auth/src/angular/two-factor-auth/two-factor-auth-component-cache.service.ts new file mode 100644 index 00000000000..61b44aa98dd --- /dev/null +++ b/libs/auth/src/angular/two-factor-auth/two-factor-auth-component-cache.service.ts @@ -0,0 +1,105 @@ +import { inject, Injectable, WritableSignal } from "@angular/core"; +import { Jsonify } from "type-fest"; + +import { ViewCacheService } from "@bitwarden/angular/platform/abstractions/view-cache.service"; +import { TwoFactorProviderType } from "@bitwarden/common/auth/enums/two-factor-provider-type"; +import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum"; +import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; + +const TWO_FACTOR_AUTH_COMPONENT_CACHE_KEY = "two-factor-auth-component-cache"; + +/** + * Cache model for the two factor authentication data. + */ +export class TwoFactorAuthComponentCache { + token: string | undefined = undefined; + remember: boolean | undefined = undefined; + selectedProviderType: TwoFactorProviderType | undefined = undefined; + + static fromJSON( + obj: Partial>, + ): TwoFactorAuthComponentCache | null { + // Return null if the cache is empty + if (obj == null) { + return null; + } + + return Object.assign(new TwoFactorAuthComponentCache(), obj); + } +} + +export interface TwoFactorAuthComponentData { + token?: string; + remember?: boolean; + selectedProviderType?: TwoFactorProviderType; +} + +/** + * Cache service used for the two factor auth component. + */ +@Injectable() +export class TwoFactorAuthComponentCacheService { + private viewCacheService: ViewCacheService = inject(ViewCacheService); + private configService: ConfigService = inject(ConfigService); + + /** True when the `PM9115_TwoFactorExtensionDataPersistence` flag is enabled */ + private featureEnabled: boolean = false; + + /** + * Signal for the cached TwoFactorAuthData. + */ + private twoFactorAuthComponentCache: WritableSignal = + this.viewCacheService.signal({ + key: TWO_FACTOR_AUTH_COMPONENT_CACHE_KEY, + initialValue: null, + deserializer: TwoFactorAuthComponentCache.fromJSON, + }); + + constructor() {} + + /** + * Must be called once before interacting with the cached data. + */ + async init() { + this.featureEnabled = await this.configService.getFeatureFlag( + FeatureFlag.PM9115_TwoFactorExtensionDataPersistence, + ); + } + + /** + * Update the cache with the new TwoFactorAuthData. + */ + cacheData(data: TwoFactorAuthComponentData): void { + if (!this.featureEnabled) { + return; + } + + this.twoFactorAuthComponentCache.set({ + token: data.token, + remember: data.remember, + selectedProviderType: data.selectedProviderType, + } as TwoFactorAuthComponentCache); + } + + /** + * Clears the cached TwoFactorAuthData. + */ + clearCachedData(): void { + if (!this.featureEnabled) { + return; + } + + this.twoFactorAuthComponentCache.set(null); + } + + /** + * Returns the cached TwoFactorAuthData (when available). + */ + getCachedData(): TwoFactorAuthComponentCache | null { + if (!this.featureEnabled) { + return null; + } + + return this.twoFactorAuthComponentCache(); + } +} diff --git a/libs/auth/src/angular/two-factor-auth/two-factor-auth.component.html b/libs/auth/src/angular/two-factor-auth/two-factor-auth.component.html index ec03944a954..8ad35c7b5c6 100644 --- a/libs/auth/src/angular/two-factor-auth/two-factor-auth.component.html +++ b/libs/auth/src/angular/two-factor-auth/two-factor-auth.component.html @@ -13,11 +13,13 @@ > {{ "dontAskAgainOnThisDeviceFor30Days" | i18n }} - + { let anonLayoutWrapperDataService: MockProxy; let mockEnvService: MockProxy; let mockLoginSuccessHandlerService: MockProxy; + let mockTwoFactorAuthCompCacheService: MockProxy; let mockUserDecryptionOpts: { noMasterPassword: UserDecryptionOptions; @@ -112,6 +112,10 @@ describe("TwoFactorAuthComponent", () => { anonLayoutWrapperDataService = mock(); + mockTwoFactorAuthCompCacheService = mock(); + mockTwoFactorAuthCompCacheService.getCachedData.mockReturnValue(null); + mockTwoFactorAuthCompCacheService.init.mockResolvedValue(); + mockUserDecryptionOpts = { noMasterPassword: new UserDecryptionOptions({ hasMasterPassword: false, @@ -155,7 +159,9 @@ describe("TwoFactorAuthComponent", () => { }), }; - selectedUserDecryptionOptions = new BehaviorSubject(undefined); + selectedUserDecryptionOptions = new BehaviorSubject( + mockUserDecryptionOpts.withMasterPassword, + ); mockUserDecryptionOptionsService.userDecryptionOptions$ = selectedUserDecryptionOptions; TestBed.configureTestingModule({ @@ -194,6 +200,10 @@ describe("TwoFactorAuthComponent", () => { { provide: EnvironmentService, useValue: mockEnvService }, { provide: AnonLayoutWrapperDataService, useValue: anonLayoutWrapperDataService }, { provide: LoginSuccessHandlerService, useValue: mockLoginSuccessHandlerService }, + { + provide: TwoFactorAuthComponentCacheService, + useValue: mockTwoFactorAuthCompCacheService, + }, ], }); diff --git a/libs/auth/src/angular/two-factor-auth/two-factor-auth.component.ts b/libs/auth/src/angular/two-factor-auth/two-factor-auth.component.ts index 6cdf42b76da..f09d7163667 100644 --- a/libs/auth/src/angular/two-factor-auth/two-factor-auth.component.ts +++ b/libs/auth/src/angular/two-factor-auth/two-factor-auth.component.ts @@ -60,6 +60,10 @@ import { TwoFactorAuthDuoComponent } from "./child-components/two-factor-auth-du import { TwoFactorAuthEmailComponent } from "./child-components/two-factor-auth-email/two-factor-auth-email.component"; import { TwoFactorAuthWebAuthnComponent } from "./child-components/two-factor-auth-webauthn/two-factor-auth-webauthn.component"; import { TwoFactorAuthYubikeyComponent } from "./child-components/two-factor-auth-yubikey.component"; +import { + TwoFactorAuthComponentCacheService, + TwoFactorAuthComponentData, +} from "./two-factor-auth-component-cache.service"; import { DuoLaunchAction, LegacyKeyMigrationAction, @@ -90,7 +94,11 @@ import { TwoFactorAuthYubikeyComponent, TwoFactorAuthWebAuthnComponent, ], - providers: [], + providers: [ + { + provide: TwoFactorAuthComponentCacheService, + }, + ], }) export class TwoFactorAuthComponent implements OnInit, OnDestroy { @ViewChild("continueButton", { read: ElementRef, static: false }) continueButton: @@ -160,6 +168,7 @@ export class TwoFactorAuthComponent implements OnInit, OnDestroy { private anonLayoutWrapperDataService: AnonLayoutWrapperDataService, private environmentService: EnvironmentService, private loginSuccessHandlerService: LoginSuccessHandlerService, + private twoFactorAuthComponentCacheService: TwoFactorAuthComponentCacheService, ) {} async ngOnInit() { @@ -168,7 +177,33 @@ export class TwoFactorAuthComponent implements OnInit, OnDestroy { this.listenForAuthnSessionTimeout(); - await this.setSelected2faProviderType(); + // Initialize the cache + await this.twoFactorAuthComponentCacheService.init(); + + // Load cached form data if available + let loadedCachedProviderType = false; + const cachedData = this.twoFactorAuthComponentCacheService.getCachedData(); + if (cachedData) { + if (cachedData.token) { + this.form.patchValue({ token: cachedData.token }); + } + if (cachedData.remember !== undefined) { + this.form.patchValue({ remember: cachedData.remember }); + } + if (cachedData.selectedProviderType !== undefined) { + this.selectedProviderType = cachedData.selectedProviderType; + loadedCachedProviderType = true; + } + } + + // If we don't have a cached provider type, set it to the default and cache it + if (!loadedCachedProviderType) { + this.selectedProviderType = await this.initializeSelected2faProviderType(); + this.twoFactorAuthComponentCacheService.cacheData({ + selectedProviderType: this.selectedProviderType, + }); + } + await this.set2faProvidersAndData(); await this.setAnonLayoutDataByTwoFactorProviderType(); @@ -181,7 +216,29 @@ export class TwoFactorAuthComponent implements OnInit, OnDestroy { this.loading = false; } - private async setSelected2faProviderType() { + /** + * Save specific form data fields to the cache + */ + async saveFormDataWithPartialData(data: Partial) { + // Get current cached data + const currentData = this.twoFactorAuthComponentCacheService.getCachedData(); + + this.twoFactorAuthComponentCacheService.cacheData({ + token: data?.token ?? currentData?.token ?? "", + remember: data?.remember ?? currentData?.remember ?? false, + selectedProviderType: data?.selectedProviderType ?? currentData?.selectedProviderType, + }); + } + + /** + * Save the remember value to the cache when the checkbox is checked or unchecked + */ + async onRememberChange() { + const rememberValue = !!this.rememberFormControl.value; + await this.saveFormDataWithPartialData({ remember: rememberValue }); + } + + private async initializeSelected2faProviderType(): Promise { const webAuthnSupported = this.platformUtilsService.supportsWebAuthn(this.win); if ( @@ -190,18 +247,19 @@ export class TwoFactorAuthComponent implements OnInit, OnDestroy { ) { const webAuthn2faResponse = this.activatedRoute.snapshot.paramMap.get("webAuthnResponse"); if (webAuthn2faResponse) { - this.selectedProviderType = TwoFactorProviderType.WebAuthn; - return; + return TwoFactorProviderType.WebAuthn; } } - this.selectedProviderType = await this.twoFactorService.getDefaultProvider(webAuthnSupported); + return await this.twoFactorService.getDefaultProvider(webAuthnSupported); } private async set2faProvidersAndData() { this.twoFactorProviders = await this.twoFactorService.getProviders(); - const providerData = this.twoFactorProviders?.get(this.selectedProviderType); - this.selectedProviderData = providerData; + if (this.selectedProviderType !== undefined) { + const providerData = this.twoFactorProviders?.get(this.selectedProviderType); + this.selectedProviderData = providerData; + } } private listenForAuthnSessionTimeout() { @@ -267,6 +325,13 @@ export class TwoFactorAuthComponent implements OnInit, OnDestroy { // In all flows but WebAuthn, the remember value is taken from the form. const rememberValue = remember ?? this.rememberFormControl.value ?? false; + // Cache form data before submitting + this.twoFactorAuthComponentCacheService.cacheData({ + token: tokenValue, + remember: rememberValue, + selectedProviderType: this.selectedProviderType, + }); + try { this.formPromise = this.loginStrategyService.logInTwoFactor( new TokenTwoFactorRequest(this.selectedProviderType, tokenValue, rememberValue), @@ -274,6 +339,7 @@ export class TwoFactorAuthComponent implements OnInit, OnDestroy { ); const authResult: AuthResult = await this.formPromise; this.logService.info("Successfully submitted two factor token"); + await this.handleAuthResult(authResult); } catch { this.logService.error("Error submitting two factor token"); @@ -299,6 +365,13 @@ export class TwoFactorAuthComponent implements OnInit, OnDestroy { this.selectedProviderType = response.type; await this.setAnonLayoutDataByTwoFactorProviderType(); + // Update the cached provider type when a new one is chosen + this.twoFactorAuthComponentCacheService.cacheData({ + token: "", + remember: false, + selectedProviderType: response.type, + }); + this.form.reset(); this.form.updateValueAndValidity(); } @@ -362,7 +435,7 @@ export class TwoFactorAuthComponent implements OnInit, OnDestroy { break; case TwoFactorProviderType.WebAuthn: this.anonLayoutWrapperDataService.setAnonLayoutWrapperData({ - pageSubtitle: this.i18nService.t("followTheStepsBelowToFinishLoggingIn"), + pageSubtitle: this.i18nService.t("followTheStepsBelowToFinishLoggingInWithSecurityKey"), pageIcon: TwoFactorAuthWebAuthnIcon, }); break; @@ -376,6 +449,9 @@ export class TwoFactorAuthComponent implements OnInit, OnDestroy { } private async handleAuthResult(authResult: AuthResult) { + // Clear form cache + this.twoFactorAuthComponentCacheService.clearCachedData(); + if (await this.handleMigrateEncryptionKey(authResult)) { return; // stop login process } diff --git a/libs/auth/src/common/services/auth-request/auth-request.service.spec.ts b/libs/auth/src/common/services/auth-request/auth-request.service.spec.ts index b5846fcfdbf..0d2df969f87 100644 --- a/libs/auth/src/common/services/auth-request/auth-request.service.spec.ts +++ b/libs/auth/src/common/services/auth-request/auth-request.service.spec.ts @@ -106,7 +106,9 @@ describe("AuthRequestService", () => { }); it("should use the master key and hash if they exist", async () => { - masterPasswordService.masterKeySubject.next({ encKey: new Uint8Array(64) } as MasterKey); + masterPasswordService.masterKeySubject.next( + new SymmetricCryptoKey(new Uint8Array(32)) as MasterKey, + ); masterPasswordService.masterKeyHashSubject.next("MASTER_KEY_HASH"); await sut.approveOrDenyAuthRequest( @@ -115,13 +117,15 @@ describe("AuthRequestService", () => { ); expect(encryptService.encapsulateKeyUnsigned).toHaveBeenCalledWith( - { encKey: new Uint8Array(64) }, + new SymmetricCryptoKey(new Uint8Array(32)), expect.anything(), ); }); it("should use the user key if the master key and hash do not exist", async () => { - keyService.getUserKey.mockResolvedValueOnce({ key: new Uint8Array(64) } as UserKey); + keyService.getUserKey.mockResolvedValueOnce( + new SymmetricCryptoKey(new Uint8Array(64)) as UserKey, + ); await sut.approveOrDenyAuthRequest( true, @@ -129,7 +133,7 @@ describe("AuthRequestService", () => { ); expect(encryptService.encapsulateKeyUnsigned).toHaveBeenCalledWith( - { key: new Uint8Array(64) }, + new SymmetricCryptoKey(new Uint8Array(64)), expect.anything(), ); }); diff --git a/libs/auth/src/common/services/auth-request/auth-request.service.ts b/libs/auth/src/common/services/auth-request/auth-request.service.ts index f4316c2e519..226403d9c8c 100644 --- a/libs/auth/src/common/services/auth-request/auth-request.service.ts +++ b/libs/auth/src/common/services/auth-request/auth-request.service.ts @@ -14,6 +14,7 @@ import { AuthRequestPushNotification } from "@bitwarden/common/models/response/n import { AppIdService } from "@bitwarden/common/platform/abstractions/app-id.service"; import { Utils } from "@bitwarden/common/platform/misc/utils"; import { EncString } from "@bitwarden/common/platform/models/domain/enc-string"; +import { SymmetricCryptoKey } from "@bitwarden/common/platform/models/domain/symmetric-crypto-key"; import { AUTH_REQUEST_DISK_LOCAL, StateProvider, @@ -120,7 +121,10 @@ export class AuthRequestService implements AuthRequestServiceAbstraction { keyToEncrypt = await this.keyService.getUserKey(); } - const encryptedKey = await this.encryptService.encapsulateKeyUnsigned(keyToEncrypt, pubKey); + const encryptedKey = await this.encryptService.encapsulateKeyUnsigned( + keyToEncrypt as SymmetricCryptoKey, + pubKey, + ); const response = new PasswordlessAuthRequest( encryptedKey.encryptedString, diff --git a/libs/auth/src/common/services/pin/pin.service.implementation.ts b/libs/auth/src/common/services/pin/pin.service.implementation.ts index 31f0c5b0177..4e363063f2f 100644 --- a/libs/auth/src/common/services/pin/pin.service.implementation.ts +++ b/libs/auth/src/common/services/pin/pin.service.implementation.ts @@ -172,9 +172,10 @@ export class PinService implements PinServiceAbstraction { const email = await firstValueFrom( this.accountService.accounts$.pipe(map((accounts) => accounts[userId].email)), ); - const kdfConfig = await this.kdfConfigService.getKdfConfig(); + const kdfConfig = await this.kdfConfigService.getKdfConfig(userId); const pinKey = await this.makePinKey(pin, email, kdfConfig); - return await this.encryptService.encrypt(userKey.key, pinKey); + + return await this.encryptService.wrapSymmetricKey(userKey, pinKey); } async storePinKeyEncryptedUserKey( @@ -292,7 +293,7 @@ export class PinService implements PinServiceAbstraction { const email = await firstValueFrom( this.accountService.accounts$.pipe(map((accounts) => accounts[userId].email)), ); - const kdfConfig = await this.kdfConfigService.getKdfConfig(); + const kdfConfig = await this.kdfConfigService.getKdfConfig(userId); const userKey: UserKey = await this.decryptUserKey( userId, diff --git a/libs/auth/src/common/services/pin/pin.service.spec.ts b/libs/auth/src/common/services/pin/pin.service.spec.ts index e0b18c74bde..5469b121f10 100644 --- a/libs/auth/src/common/services/pin/pin.service.spec.ts +++ b/libs/auth/src/common/services/pin/pin.service.spec.ts @@ -170,7 +170,7 @@ describe("PinService", () => { await sut.createPinKeyEncryptedUserKey(mockPin, mockUserKey, mockUserId); // Assert - expect(encryptService.encrypt).toHaveBeenCalledWith(mockUserKey.key, mockPinKey); + expect(encryptService.wrapSymmetricKey).toHaveBeenCalledWith(mockUserKey, mockPinKey); }); }); @@ -434,7 +434,7 @@ describe("PinService", () => { .fn() .mockResolvedValue(pinKeyEncryptedUserKeyPersistant); sut.makePinKey = jest.fn().mockResolvedValue(mockPinKey); - encryptService.decryptToBytes.mockResolvedValue(mockUserKey.key); + encryptService.decryptToBytes.mockResolvedValue(mockUserKey.toEncoded()); } function mockPinEncryptedKeyDataByPinLockType(pinLockType: PinLockType) { diff --git a/libs/common/src/admin-console/models/request/organization/organization-sponsorship-create.request.ts b/libs/common/src/admin-console/models/request/organization/organization-sponsorship-create.request.ts index 19e993487c2..726bd6a85e1 100644 --- a/libs/common/src/admin-console/models/request/organization/organization-sponsorship-create.request.ts +++ b/libs/common/src/admin-console/models/request/organization/organization-sponsorship-create.request.ts @@ -6,5 +6,6 @@ export class OrganizationSponsorshipCreateRequest { sponsoredEmail: string; planSponsorshipType: PlanSponsorshipType; friendlyName: string; + isAdminInitiated?: boolean; notes?: string; } diff --git a/libs/common/src/admin-console/models/request/provider/provider-setup.request.ts b/libs/common/src/admin-console/models/request/provider/provider-setup.request.ts index d4992e969dc..5c9ea5526a0 100644 --- a/libs/common/src/admin-console/models/request/provider/provider-setup.request.ts +++ b/libs/common/src/admin-console/models/request/provider/provider-setup.request.ts @@ -1,6 +1,7 @@ // FIXME: Update this file to be type safe and remove this and next line // @ts-strict-ignore import { ExpandedTaxInfoUpdateRequest } from "../../../../billing/models/request/expanded-tax-info-update.request"; +import { TokenizedPaymentSourceRequest } from "../../../../billing/models/request/tokenized-payment-source.request"; export class ProviderSetupRequest { name: string; @@ -9,4 +10,5 @@ export class ProviderSetupRequest { token: string; key: string; taxInfo: ExpandedTaxInfoUpdateRequest; + paymentSource?: TokenizedPaymentSourceRequest; } diff --git a/libs/common/src/auth/abstractions/devices/responses/device.response.ts b/libs/common/src/auth/abstractions/devices/responses/device.response.ts index 84a2fb03c28..6b7f17f65ce 100644 --- a/libs/common/src/auth/abstractions/devices/responses/device.response.ts +++ b/libs/common/src/auth/abstractions/devices/responses/device.response.ts @@ -15,6 +15,9 @@ export class DeviceResponse extends BaseResponse { creationDate: string; revisionDate: string; isTrusted: boolean; + encryptedUserKey: string | null; + encryptedPublicKey: string | null; + devicePendingAuthRequest: DevicePendingAuthRequest | null; constructor(response: any) { @@ -27,6 +30,8 @@ export class DeviceResponse extends BaseResponse { this.creationDate = this.getResponseProperty("CreationDate"); this.revisionDate = this.getResponseProperty("RevisionDate"); this.isTrusted = this.getResponseProperty("IsTrusted"); + this.encryptedUserKey = this.getResponseProperty("EncryptedUserKey"); + this.encryptedPublicKey = this.getResponseProperty("EncryptedPublicKey"); this.devicePendingAuthRequest = this.getResponseProperty("DevicePendingAuthRequest"); } } diff --git a/libs/common/src/auth/services/user-verification/user-verification.service.ts b/libs/common/src/auth/services/user-verification/user-verification.service.ts index 1ff629114ab..cfa6800deed 100644 --- a/libs/common/src/auth/services/user-verification/user-verification.service.ts +++ b/libs/common/src/auth/services/user-verification/user-verification.service.ts @@ -117,7 +117,7 @@ export class UserVerificationService implements UserVerificationServiceAbstracti masterKey = await this.keyService.makeMasterKey( verification.secret, email, - await this.kdfConfigService.getKdfConfig(), + await this.kdfConfigService.getKdfConfig(userId), ); } request.masterPasswordHash = alreadyHashed @@ -186,7 +186,7 @@ export class UserVerificationService implements UserVerificationServiceAbstracti throw new Error("Email is required. Cannot verify user by master password."); } - const kdfConfig = await this.kdfConfigService.getKdfConfig(); + const kdfConfig = await this.kdfConfigService.getKdfConfig(userId); if (!kdfConfig) { throw new Error("KDF config is required. Cannot verify user by master password."); } diff --git a/libs/common/src/auth/services/webauthn-login/webauthn-login-prf-key.service.spec.ts b/libs/common/src/auth/services/webauthn-login/webauthn-login-prf-key.service.spec.ts index 614c593048d..eca1f188d85 100644 --- a/libs/common/src/auth/services/webauthn-login/webauthn-login-prf-key.service.spec.ts +++ b/libs/common/src/auth/services/webauthn-login/webauthn-login-prf-key.service.spec.ts @@ -21,7 +21,7 @@ describe("WebAuthnLoginPrfKeyService", () => { const result = await service.createSymmetricKeyFromPrf(randomBytes(32)); - expect(result.key.length).toBe(64); + expect(result.toEncoded().length).toBe(64); }); }); }); diff --git a/libs/common/src/billing/abstractions/organizations/organization-billing-api.service.abstraction.ts b/libs/common/src/billing/abstractions/organizations/organization-billing-api.service.abstraction.ts index e1f7ad49012..4975da0d7d2 100644 --- a/libs/common/src/billing/abstractions/organizations/organization-billing-api.service.abstraction.ts +++ b/libs/common/src/billing/abstractions/organizations/organization-billing-api.service.abstraction.ts @@ -1,3 +1,5 @@ +import { OrganizationWarningsResponse } from "@bitwarden/common/billing/models/response/organization-warnings.response"; + import { BillingInvoiceResponse, BillingTransactionResponse, @@ -15,6 +17,8 @@ export abstract class OrganizationBillingApiServiceAbstraction { startAfter?: string, ) => Promise; + abstract getWarnings: (id: string) => Promise; + abstract setupBusinessUnit: ( id: string, request: { diff --git a/libs/common/src/billing/abstractions/organizations/organization-sponsorship-api.service.abstraction.ts b/libs/common/src/billing/abstractions/organizations/organization-sponsorship-api.service.abstraction.ts new file mode 100644 index 00000000000..e6e395c69df --- /dev/null +++ b/libs/common/src/billing/abstractions/organizations/organization-sponsorship-api.service.abstraction.ts @@ -0,0 +1,8 @@ +import { ListResponse } from "../../../models/response/list.response"; +import { OrganizationSponsorshipInvitesResponse } from "../../models/response/organization-sponsorship-invites.response"; + +export abstract class OrganizationSponsorshipApiServiceAbstraction { + abstract getOrganizationSponsorship( + sponsoredOrgId: string, + ): Promise>; +} diff --git a/libs/common/src/billing/models/response/organization-sponsorship-invites.response.ts b/libs/common/src/billing/models/response/organization-sponsorship-invites.response.ts new file mode 100644 index 00000000000..87a2cae4699 --- /dev/null +++ b/libs/common/src/billing/models/response/organization-sponsorship-invites.response.ts @@ -0,0 +1,31 @@ +import { BaseResponse } from "../../../models/response/base.response"; +import { PlanSponsorshipType } from "../../enums"; + +export class OrganizationSponsorshipInvitesResponse extends BaseResponse { + sponsoringOrganizationUserId: string; + friendlyName: string; + offeredToEmail: string; + planSponsorshipType: PlanSponsorshipType; + lastSyncDate?: Date; + validUntil?: Date; + toDelete = false; + isAdminInitiated: boolean; + notes: string; + statusMessage?: string; + statusClass?: string; + + constructor(response: any) { + super(response); + this.sponsoringOrganizationUserId = this.getResponseProperty("SponsoringOrganizationUserId"); + this.friendlyName = this.getResponseProperty("FriendlyName"); + this.offeredToEmail = this.getResponseProperty("OfferedToEmail"); + this.planSponsorshipType = this.getResponseProperty("PlanSponsorshipType"); + this.lastSyncDate = this.getResponseProperty("LastSyncDate"); + this.validUntil = this.getResponseProperty("ValidUntil"); + this.toDelete = this.getResponseProperty("ToDelete") ?? false; + this.isAdminInitiated = this.getResponseProperty("IsAdminInitiated"); + this.notes = this.getResponseProperty("Notes"); + this.statusMessage = this.getResponseProperty("StatusMessage"); + this.statusClass = this.getResponseProperty("StatusClass"); + } +} diff --git a/libs/common/src/billing/models/response/organization-warnings.response.ts b/libs/common/src/billing/models/response/organization-warnings.response.ts new file mode 100644 index 00000000000..ff70298101e --- /dev/null +++ b/libs/common/src/billing/models/response/organization-warnings.response.ts @@ -0,0 +1,97 @@ +import { BaseResponse } from "../../../models/response/base.response"; + +export class OrganizationWarningsResponse extends BaseResponse { + freeTrial?: FreeTrialWarningResponse; + inactiveSubscription?: InactiveSubscriptionWarningResponse; + resellerRenewal?: ResellerRenewalWarningResponse; + + constructor(response: any) { + super(response); + const freeTrialWarning = this.getResponseProperty("FreeTrial"); + if (freeTrialWarning) { + this.freeTrial = new FreeTrialWarningResponse(freeTrialWarning); + } + const inactiveSubscriptionWarning = this.getResponseProperty("InactiveSubscription"); + if (inactiveSubscriptionWarning) { + this.inactiveSubscription = new InactiveSubscriptionWarningResponse( + inactiveSubscriptionWarning, + ); + } + const resellerWarning = this.getResponseProperty("ResellerRenewal"); + if (resellerWarning) { + this.resellerRenewal = new ResellerRenewalWarningResponse(resellerWarning); + } + } +} + +class FreeTrialWarningResponse extends BaseResponse { + remainingTrialDays: number; + + constructor(response: any) { + super(response); + this.remainingTrialDays = this.getResponseProperty("RemainingTrialDays"); + } +} + +class InactiveSubscriptionWarningResponse extends BaseResponse { + resolution: string; + + constructor(response: any) { + super(response); + this.resolution = this.getResponseProperty("Resolution"); + } +} + +class ResellerRenewalWarningResponse extends BaseResponse { + type: "upcoming" | "issued" | "past_due"; + upcoming?: UpcomingRenewal; + issued?: IssuedRenewal; + pastDue?: PastDueRenewal; + + constructor(response: any) { + super(response); + this.type = this.getResponseProperty("Type"); + switch (this.type) { + case "upcoming": { + this.upcoming = new UpcomingRenewal(this.getResponseProperty("Upcoming")); + break; + } + case "issued": { + this.issued = new IssuedRenewal(this.getResponseProperty("Issued")); + break; + } + case "past_due": { + this.pastDue = new PastDueRenewal(this.getResponseProperty("PastDue")); + } + } + } +} + +class UpcomingRenewal extends BaseResponse { + renewalDate: Date; + + constructor(response: any) { + super(response); + this.renewalDate = new Date(this.getResponseProperty("RenewalDate")); + } +} + +class IssuedRenewal extends BaseResponse { + issuedDate: Date; + dueDate: Date; + + constructor(response: any) { + super(response); + this.issuedDate = new Date(this.getResponseProperty("IssuedDate")); + this.dueDate = new Date(this.getResponseProperty("DueDate")); + } +} + +class PastDueRenewal extends BaseResponse { + suspensionDate: Date; + + constructor(response: any) { + super(response); + this.suspensionDate = new Date(this.getResponseProperty("SuspensionDate")); + } +} diff --git a/libs/common/src/billing/services/organization/organization-billing-api.service.ts b/libs/common/src/billing/services/organization/organization-billing-api.service.ts index 405bd41957f..1189316a487 100644 --- a/libs/common/src/billing/services/organization/organization-billing-api.service.ts +++ b/libs/common/src/billing/services/organization/organization-billing-api.service.ts @@ -1,3 +1,5 @@ +import { OrganizationWarningsResponse } from "@bitwarden/common/billing/models/response/organization-warnings.response"; + import { ApiService } from "../../../abstractions/api.service"; import { OrganizationBillingApiServiceAbstraction } from "../../abstractions/organizations/organization-billing-api.service.abstraction"; import { @@ -50,6 +52,18 @@ export class OrganizationBillingApiService implements OrganizationBillingApiServ return r?.map((i: any) => new BillingTransactionResponse(i)) || []; } + async getWarnings(id: string): Promise { + const response = await this.apiService.send( + "GET", + `/organizations/${id}/billing/warnings`, + null, + true, + true, + ); + + return new OrganizationWarningsResponse(response); + } + async setupBusinessUnit( id: string, request: { diff --git a/libs/common/src/billing/services/organization/organization-sponsorship-api.service.ts b/libs/common/src/billing/services/organization/organization-sponsorship-api.service.ts new file mode 100644 index 00000000000..bb420377439 --- /dev/null +++ b/libs/common/src/billing/services/organization/organization-sponsorship-api.service.ts @@ -0,0 +1,22 @@ +import { ApiService } from "../../../abstractions/api.service"; +import { ListResponse } from "../../../models/response/list.response"; +import { OrganizationSponsorshipApiServiceAbstraction } from "../../abstractions/organizations/organization-sponsorship-api.service.abstraction"; +import { OrganizationSponsorshipInvitesResponse } from "../../models/response/organization-sponsorship-invites.response"; + +export class OrganizationSponsorshipApiService + implements OrganizationSponsorshipApiServiceAbstraction +{ + constructor(private apiService: ApiService) {} + async getOrganizationSponsorship( + sponsoredOrgId: string, + ): Promise> { + const r = await this.apiService.send( + "GET", + "/organization/sponsorship/" + sponsoredOrgId + "/sponsored", + null, + true, + true, + ); + return new ListResponse(r, OrganizationSponsorshipInvitesResponse); + } +} diff --git a/libs/common/src/enums/feature-flag.enum.ts b/libs/common/src/enums/feature-flag.enum.ts index 6c8e8534e42..9f03d83ab34 100644 --- a/libs/common/src/enums/feature-flag.enum.ts +++ b/libs/common/src/enums/feature-flag.enum.ts @@ -17,6 +17,7 @@ export enum FeatureFlag { /* Auth */ PM9112_DeviceApprovalPersistence = "pm-9112-device-approval-persistence", + PM9115_TwoFactorExtensionDataPersistence = "pm-9115-two-factor-extension-data-persistence", /* Autofill */ BlockBrowserInjectionsByDomain = "block-browser-injections-by-domain", @@ -30,10 +31,15 @@ export enum FeatureFlag { /* Billing */ TrialPaymentOptional = "PM-8163-trial-payment", - PM15179_AddExistingOrgsFromProviderPortal = "pm-15179-add-existing-orgs-from-provider-portal", PM12276_BreadcrumbEventLogs = "pm-12276-breadcrumbing-for-business-features", PM18794_ProviderPaymentMethod = "pm-18794-provider-payment-method", PM17772_AdminInitiatedSponsorships = "pm-17772-admin-initiated-sponsorships", + PM19956_RequireProviderPaymentMethodDuringSetup = "pm-19956-require-provider-payment-method-during-setup", + UseOrganizationWarningsService = "use-organization-warnings-service", + + /* Data Insights and Reporting */ + CriticalApps = "pm-14466-risk-insights-critical-application", + EnableRiskInsightsNotifications = "enable-risk-insights-notifications", /* Key Management */ PrivateKeyRegeneration = "pm-12241-private-key-regeneration", @@ -43,8 +49,6 @@ export enum FeatureFlag { /* Tools */ ItemShare = "item-share", - CriticalApps = "pm-14466-risk-insights-critical-application", - EnableRiskInsightsNotifications = "enable-risk-insights-notifications", DesktopSendUIRefresh = "desktop-send-ui-refresh", /* Vault */ @@ -52,9 +56,10 @@ export enum FeatureFlag { PM9111ExtensionPersistAddEditForm = "pm-9111-extension-persist-add-edit-form", NewDeviceVerificationTemporaryDismiss = "new-device-temporary-dismiss", NewDeviceVerificationPermanentDismiss = "new-device-permanent-dismiss", - VaultBulkManagementAction = "vault-bulk-management-action", SecurityTasks = "security-tasks", CipherKeyEncryption = "cipher-key-encryption", + PM18520_UpdateDesktopCipherForm = "pm-18520-desktop-cipher-forms", + EndUserNotifications = "pm-10609-end-user-notifications", /* Platform */ IpcChannelFramework = "ipc-channel-framework", @@ -94,10 +99,12 @@ export const DefaultFeatureFlagValue = { [FeatureFlag.UseTreeWalkerApiForPageDetailsCollection]: FALSE, [FeatureFlag.MacOsNativeCredentialSync]: FALSE, - /* Tools */ - [FeatureFlag.ItemShare]: FALSE, + /* Data Insights and Reporting */ [FeatureFlag.CriticalApps]: FALSE, [FeatureFlag.EnableRiskInsightsNotifications]: FALSE, + + /* Tools */ + [FeatureFlag.ItemShare]: FALSE, [FeatureFlag.DesktopSendUIRefresh]: FALSE, /* Vault */ @@ -105,19 +112,22 @@ export const DefaultFeatureFlagValue = { [FeatureFlag.PM9111ExtensionPersistAddEditForm]: FALSE, [FeatureFlag.NewDeviceVerificationTemporaryDismiss]: FALSE, [FeatureFlag.NewDeviceVerificationPermanentDismiss]: FALSE, - [FeatureFlag.VaultBulkManagementAction]: FALSE, [FeatureFlag.SecurityTasks]: FALSE, [FeatureFlag.CipherKeyEncryption]: FALSE, + [FeatureFlag.PM18520_UpdateDesktopCipherForm]: FALSE, + [FeatureFlag.EndUserNotifications]: FALSE, /* Auth */ [FeatureFlag.PM9112_DeviceApprovalPersistence]: FALSE, + [FeatureFlag.PM9115_TwoFactorExtensionDataPersistence]: FALSE, /* Billing */ [FeatureFlag.TrialPaymentOptional]: FALSE, - [FeatureFlag.PM15179_AddExistingOrgsFromProviderPortal]: FALSE, [FeatureFlag.PM12276_BreadcrumbEventLogs]: FALSE, [FeatureFlag.PM18794_ProviderPaymentMethod]: FALSE, [FeatureFlag.PM17772_AdminInitiatedSponsorships]: FALSE, + [FeatureFlag.PM19956_RequireProviderPaymentMethodDuringSetup]: FALSE, + [FeatureFlag.UseOrganizationWarningsService]: FALSE, /* Key Management */ [FeatureFlag.PrivateKeyRegeneration]: FALSE, diff --git a/libs/common/src/key-management/crypto/abstractions/encrypt.service.ts b/libs/common/src/key-management/crypto/abstractions/encrypt.service.ts index a1e54f7064f..7a6c9bcd800 100644 --- a/libs/common/src/key-management/crypto/abstractions/encrypt.service.ts +++ b/libs/common/src/key-management/crypto/abstractions/encrypt.service.ts @@ -7,9 +7,22 @@ import { EncString } from "../../../platform/models/domain/enc-string"; import { SymmetricCryptoKey } from "../../../platform/models/domain/symmetric-crypto-key"; export abstract class EncryptService { + /** + * @deprecated + * Encrypts a string or Uint8Array to an EncString + * @param plainValue - The value to encrypt + * @param key - The key to encrypt the value with + */ abstract encrypt(plainValue: string | Uint8Array, key: SymmetricCryptoKey): Promise; + /** + * @deprecated + * Encrypts a value to a Uint8Array + * @param plainValue - The value to encrypt + * @param key - The key to encrypt the value with + */ abstract encryptToBytes(plainValue: Uint8Array, key: SymmetricCryptoKey): Promise; /** + * @deprecated * Decrypts an EncString to a string * @param encString - The EncString to decrypt * @param key - The key to decrypt the EncString with @@ -23,6 +36,7 @@ export abstract class EncryptService { decryptTrace?: string, ): Promise; /** + * @deprecated * Decrypts an Encrypted object to a Uint8Array * @param encThing - The Encrypted object to decrypt * @param key - The key to decrypt the Encrypted object with @@ -35,10 +49,125 @@ export abstract class EncryptService { key: SymmetricCryptoKey, decryptTrace?: string, ): Promise; + /** + * @deprecated Replaced by BulkEncryptService, remove once the feature is tested and the featureflag PM-4154-multi-worker-encryption-service is removed + * @param items The items to decrypt + * @param key The key to decrypt the items with + */ + abstract decryptItems( + items: Decryptable[], + key: SymmetricCryptoKey, + ): Promise; + + /** + * Encrypts a string to an EncString + * @param plainValue - The value to encrypt + * @param key - The key to encrypt the value with + */ + abstract encryptString(plainValue: string, key: SymmetricCryptoKey): Promise; + /** + * Encrypts bytes to an EncString + * @param plainValue - The value to encrypt + * @param key - The key to encrypt the value with + * @deprecated Bytes are not the right abstraction to encrypt in. Use e.g. key wrapping or file encryption instead + */ + abstract encryptBytes(plainValue: Uint8Array, key: SymmetricCryptoKey): Promise; + /** + * Encrypts a value to a Uint8Array + * @param plainValue - The value to encrypt + * @param key - The key to encrypt the value with + */ + abstract encryptFileData( + plainValue: Uint8Array, + key: SymmetricCryptoKey, + ): Promise; + + /** + * Decrypts an EncString to a string + * @param encString - The EncString containing the encrypted string. + * @param key - The key to decrypt the value with + */ + abstract decryptString(encString: EncString, key: SymmetricCryptoKey): Promise; + /** + * Decrypts an EncString to a Uint8Array + * @param encString - The EncString containing the encrypted bytes. + * @param key - The key to decrypt the value with + * @deprecated Bytes are not the right abstraction to encrypt in. Use e.g. key wrapping or file encryption instead + */ + abstract decryptBytes(encString: EncString, key: SymmetricCryptoKey): Promise; + /** + * Decrypts an EncArrayBuffer to a Uint8Array + * @param encBuffer - The EncArrayBuffer containing the encrypted file bytes. + * @param key - The key to decrypt the value with + */ + abstract decryptFileData(encBuffer: EncArrayBuffer, key: SymmetricCryptoKey): Promise; + + /** + * Wraps a decapsulation key (Private key) with a symmetric key + * @see {@link https://en.wikipedia.org/wiki/Key_wrap} + * @param decapsulationKeyPcks8 - The private key in PKCS8 format + * @param wrappingKey - The symmetric key to wrap the private key with + */ + abstract wrapDecapsulationKey( + decapsulationKeyPcks8: Uint8Array, + wrappingKey: SymmetricCryptoKey, + ): Promise; + /** + * Wraps an encapsulation key (Public key) with a symmetric key + * @see {@link https://en.wikipedia.org/wiki/Key_wrap} + * @param encapsulationKeySpki - The public key in SPKI format + * @param wrappingKey - The symmetric key to wrap the public key with + */ + abstract wrapEncapsulationKey( + encapsulationKeySpki: Uint8Array, + wrappingKey: SymmetricCryptoKey, + ): Promise; + /** + * Wraps a symmetric key with another symmetric key + * @see {@link https://en.wikipedia.org/wiki/Key_wrap} + * @param keyToBeWrapped - The symmetric key to wrap + * @param wrappingKey - The symmetric key to wrap the encapsulated key with + */ + abstract wrapSymmetricKey( + keyToBeWrapped: SymmetricCryptoKey, + wrappingKey: SymmetricCryptoKey, + ): Promise; + + /** + * Unwraps a decapsulation key (Private key) with a symmetric key + * @see {@link https://en.wikipedia.org/wiki/Key_wrap} + * @param decapsulationKeyPcks8 - The private key in PKCS8 format + * @param wrappingKey - The symmetric key to wrap the private key with + */ + abstract unwrapDecapsulationKey( + wrappedDecapsulationKey: EncString, + wrappingKey: SymmetricCryptoKey, + ): Promise; + /** + * Wraps an encapsulation key (Public key) with a symmetric key + * @see {@link https://en.wikipedia.org/wiki/Key_wrap} + * @param encapsulationKeySpki - The public key in SPKI format + * @param wrappingKey - The symmetric key to wrap the public key with + */ + abstract unwrapEncapsulationKey( + wrappedEncapsulationKey: EncString, + wrappingKey: SymmetricCryptoKey, + ): Promise; + /** + * Unwraps a symmetric key with another symmetric key + * @see {@link https://en.wikipedia.org/wiki/Key_wrap} + * @param keyToBeWrapped - The symmetric key to wrap + * @param wrappingKey - The symmetric key to wrap the encapsulated key with + */ + abstract unwrapSymmetricKey( + keyToBeUnwrapped: EncString, + wrappingKey: SymmetricCryptoKey, + ): Promise; /** * Encapsulates a symmetric key with an asymmetric public key * Note: This does not establish sender authenticity + * @see {@link https://en.wikipedia.org/wiki/Key_encapsulation_mechanism} * @param sharedKey - The symmetric key that is to be shared * @param encapsulationKey - The encapsulation key (public key) of the receiver that the key is shared with */ @@ -49,6 +178,7 @@ export abstract class EncryptService { /** * Decapsulates a shared symmetric key with an asymmetric private key * Note: This does not establish sender authenticity + * @see {@link https://en.wikipedia.org/wiki/Key_encapsulation_mechanism} * @param encryptedSharedKey - The encrypted shared symmetric key * @param decapsulationKey - The key to decapsulate with (private key) */ @@ -56,27 +186,20 @@ export abstract class EncryptService { encryptedSharedKey: EncString, decapsulationKey: Uint8Array, ): Promise; + /** - * @deprecated Use encapsulateKeyUnsigned instead + * @deprecated Use @see {@link encapsulateKeyUnsigned} instead * @param data - The data to encrypt * @param publicKey - The public key to encrypt with */ abstract rsaEncrypt(data: Uint8Array, publicKey: Uint8Array): Promise; /** - * @deprecated Use decapsulateKeyUnsigned instead + * @deprecated Use @see {@link decapsulateKeyUnsigned} instead * @param data - The ciphertext to decrypt * @param privateKey - The privateKey to decrypt with */ abstract rsaDecrypt(data: EncString, privateKey: Uint8Array): Promise; - /** - * @deprecated Replaced by BulkEncryptService, remove once the feature is tested and the featureflag PM-4154-multi-worker-encryption-service is removed - * @param items The items to decrypt - * @param key The key to decrypt the items with - */ - abstract decryptItems( - items: Decryptable[], - key: SymmetricCryptoKey, - ): Promise; + /** * Generates a base64-encoded hash of the given value * @param value The value to hash diff --git a/libs/common/src/key-management/crypto/services/encrypt.service.implementation.ts b/libs/common/src/key-management/crypto/services/encrypt.service.implementation.ts index 4b299c9c6e6..fb0649f7c5b 100644 --- a/libs/common/src/key-management/crypto/services/encrypt.service.implementation.ts +++ b/libs/common/src/key-management/crypto/services/encrypt.service.implementation.ts @@ -36,6 +36,100 @@ export class EncryptServiceImplementation implements EncryptService { protected logMacFailures: boolean, ) {} + // Proxy functions; Their implementation are temporary before moving at this level to the SDK + async encryptString(plainValue: string, key: SymmetricCryptoKey): Promise { + return this.encrypt(plainValue, key); + } + + async encryptBytes(plainValue: Uint8Array, key: SymmetricCryptoKey): Promise { + return this.encrypt(plainValue, key); + } + + async encryptFileData(plainValue: Uint8Array, key: SymmetricCryptoKey): Promise { + return this.encryptToBytes(plainValue, key); + } + + async decryptString(encString: EncString, key: SymmetricCryptoKey): Promise { + return this.decryptToUtf8(encString, key); + } + + async decryptBytes(encString: EncString, key: SymmetricCryptoKey): Promise { + return this.decryptToBytes(encString, key); + } + + async decryptFileData(encBuffer: EncArrayBuffer, key: SymmetricCryptoKey): Promise { + return this.decryptToBytes(encBuffer, key); + } + + async wrapDecapsulationKey( + decapsulationKeyPkcs8: Uint8Array, + wrappingKey: SymmetricCryptoKey, + ): Promise { + if (decapsulationKeyPkcs8 == null) { + throw new Error("No decapsulation key provided for wrapping."); + } + + if (wrappingKey == null) { + throw new Error("No wrappingKey provided for wrapping."); + } + + return await this.encryptUint8Array(decapsulationKeyPkcs8, wrappingKey); + } + + async wrapEncapsulationKey( + encapsulationKeySpki: Uint8Array, + wrappingKey: SymmetricCryptoKey, + ): Promise { + if (encapsulationKeySpki == null) { + throw new Error("No encapsulation key provided for wrapping."); + } + + if (wrappingKey == null) { + throw new Error("No wrappingKey provided for wrapping."); + } + + return await this.encryptUint8Array(encapsulationKeySpki, wrappingKey); + } + + async wrapSymmetricKey( + keyToBeWrapped: SymmetricCryptoKey, + wrappingKey: SymmetricCryptoKey, + ): Promise { + if (keyToBeWrapped == null) { + throw new Error("No keyToBeWrapped provided for wrapping."); + } + + if (wrappingKey == null) { + throw new Error("No wrappingKey provided for wrapping."); + } + + return await this.encryptUint8Array(keyToBeWrapped.toEncoded(), wrappingKey); + } + + async unwrapDecapsulationKey( + wrappedDecapsulationKey: EncString, + wrappingKey: SymmetricCryptoKey, + ): Promise { + return this.decryptBytes(wrappedDecapsulationKey, wrappingKey); + } + async unwrapEncapsulationKey( + wrappedEncapsulationKey: EncString, + wrappingKey: SymmetricCryptoKey, + ): Promise { + return this.decryptBytes(wrappedEncapsulationKey, wrappingKey); + } + async unwrapSymmetricKey( + keyToBeUnwrapped: EncString, + wrappingKey: SymmetricCryptoKey, + ): Promise { + return new SymmetricCryptoKey(await this.decryptBytes(keyToBeUnwrapped, wrappingKey)); + } + + async hash(value: string | Uint8Array, algorithm: "sha1" | "sha256" | "sha512"): Promise { + const hashArray = await this.cryptoFunctionService.hash(value, algorithm); + return Utils.fromBufferToB64(hashArray); + } + // Handle updating private properties to turn on/off feature flags. onServerConfigChange(newConfig: ServerConfig): void { this.blockType0 = getFeatureFlagValue(newConfig, FeatureFlag.PM17987_BlockType0); @@ -47,7 +141,7 @@ export class EncryptServiceImplementation implements EncryptService { } if (this.blockType0) { - if (key.encType === EncryptionType.AesCbc256_B64 || key.key.byteLength < 64) { + if (key.inner().type === EncryptionType.AesCbc256_B64) { throw new Error("Type 0 encryption is not supported."); } } @@ -56,22 +150,40 @@ export class EncryptServiceImplementation implements EncryptService { return Promise.resolve(null); } - let plainBuf: Uint8Array; if (typeof plainValue === "string") { - plainBuf = Utils.fromUtf8ToArray(plainValue); + return this.encryptUint8Array(Utils.fromUtf8ToArray(plainValue), key); } else { - plainBuf = plainValue; + return this.encryptUint8Array(plainValue, key); + } + } + + private async encryptUint8Array( + plainValue: Uint8Array, + key: SymmetricCryptoKey, + ): Promise { + if (key == null) { + throw new Error("No encryption key provided."); + } + + if (this.blockType0) { + if (key.inner().type === EncryptionType.AesCbc256_B64) { + throw new Error("Type 0 encryption is not supported."); + } + } + + if (plainValue == null) { + return Promise.resolve(null); } const innerKey = key.inner(); if (innerKey.type === EncryptionType.AesCbc256_HmacSha256_B64) { - const encObj = await this.aesEncrypt(plainBuf, innerKey); + const encObj = await this.aesEncrypt(plainValue, innerKey); const iv = Utils.fromBufferToB64(encObj.iv); const data = Utils.fromBufferToB64(encObj.data); const mac = Utils.fromBufferToB64(encObj.mac); return new EncString(innerKey.type, data, iv, mac); } else if (innerKey.type === EncryptionType.AesCbc256_B64) { - const encObj = await this.aesEncryptLegacy(plainBuf, innerKey); + const encObj = await this.aesEncryptLegacy(plainValue, innerKey); const iv = Utils.fromBufferToB64(encObj.iv); const data = Utils.fromBufferToB64(encObj.data); return new EncString(innerKey.type, data, iv); @@ -84,7 +196,7 @@ export class EncryptServiceImplementation implements EncryptService { } if (this.blockType0) { - if (key.encType === EncryptionType.AesCbc256_B64 || key.key.byteLength < 64) { + if (key.inner().type === EncryptionType.AesCbc256_B64) { throw new Error("Type 0 encryption is not supported."); } } @@ -124,7 +236,7 @@ export class EncryptServiceImplementation implements EncryptService { if (encString.encryptionType !== innerKey.type) { this.logDecryptError( "Key encryption type does not match payload encryption type", - key.encType, + innerKey.type, encString.encryptionType, decryptContext, ); @@ -148,7 +260,7 @@ export class EncryptServiceImplementation implements EncryptService { if (!macsEqual) { this.logMacFailed( "decryptToUtf8 MAC comparison failed. Key or payload has changed.", - key.encType, + innerKey.type, encString.encryptionType, decryptContext, ); @@ -191,7 +303,7 @@ export class EncryptServiceImplementation implements EncryptService { if (encThing.encryptionType !== inner.type) { this.logDecryptError( "Encryption key type mismatch", - key.encType, + inner.type, encThing.encryptionType, decryptContext, ); @@ -200,19 +312,23 @@ export class EncryptServiceImplementation implements EncryptService { if (inner.type === EncryptionType.AesCbc256_HmacSha256_B64) { if (encThing.macBytes == null) { - this.logDecryptError("Mac missing", key.encType, encThing.encryptionType, decryptContext); + this.logDecryptError("Mac missing", inner.type, encThing.encryptionType, decryptContext); return null; } const macData = new Uint8Array(encThing.ivBytes.byteLength + encThing.dataBytes.byteLength); macData.set(new Uint8Array(encThing.ivBytes), 0); macData.set(new Uint8Array(encThing.dataBytes), encThing.ivBytes.byteLength); - const computedMac = await this.cryptoFunctionService.hmac(macData, key.macKey, "sha256"); + const computedMac = await this.cryptoFunctionService.hmac( + macData, + inner.authenticationKey, + "sha256", + ); const macsMatch = await this.cryptoFunctionService.compare(encThing.macBytes, computedMac); if (!macsMatch) { this.logMacFailed( "MAC comparison failed. Key or payload has changed.", - key.encType, + inner.type, encThing.encryptionType, decryptContext, ); @@ -222,14 +338,14 @@ export class EncryptServiceImplementation implements EncryptService { return await this.cryptoFunctionService.aesDecrypt( encThing.dataBytes, encThing.ivBytes, - key.encKey, + inner.encryptionKey, "cbc", ); } else if (inner.type === EncryptionType.AesCbc256_B64) { return await this.cryptoFunctionService.aesDecrypt( encThing.dataBytes, encThing.ivBytes, - key.encKey, + inner.encryptionKey, "cbc", ); } @@ -272,11 +388,6 @@ export class EncryptServiceImplementation implements EncryptService { return results; } - async hash(value: string | Uint8Array, algorithm: "sha1" | "sha256" | "sha512"): Promise { - const hashArray = await this.cryptoFunctionService.hash(value, algorithm); - return Utils.fromBufferToB64(hashArray); - } - private async aesEncrypt(data: Uint8Array, key: Aes256CbcHmacKey): Promise { const obj = new EncryptedObject(); obj.iv = await this.cryptoFunctionService.randomBytes(16); diff --git a/libs/common/src/key-management/crypto/services/encrypt.service.spec.ts b/libs/common/src/key-management/crypto/services/encrypt.service.spec.ts index 4cbe3a3da90..d19de6c0414 100644 --- a/libs/common/src/key-management/crypto/services/encrypt.service.spec.ts +++ b/libs/common/src/key-management/crypto/services/encrypt.service.spec.ts @@ -6,7 +6,10 @@ import { EncryptionType } from "@bitwarden/common/platform/enums"; import { Utils } from "@bitwarden/common/platform/misc/utils"; import { EncArrayBuffer } from "@bitwarden/common/platform/models/domain/enc-array-buffer"; import { EncString } from "@bitwarden/common/platform/models/domain/enc-string"; -import { SymmetricCryptoKey } from "@bitwarden/common/platform/models/domain/symmetric-crypto-key"; +import { + Aes256CbcHmacKey, + SymmetricCryptoKey, +} from "@bitwarden/common/platform/models/domain/symmetric-crypto-key"; import { CsprngArray } from "@bitwarden/common/types/csprng"; import { makeStaticByteArray } from "../../../../spec"; @@ -28,6 +31,124 @@ describe("EncryptService", () => { encryptService = new EncryptServiceImplementation(cryptoFunctionService, logService, true); }); + describe("wrapSymmetricKey", () => { + it("roundtrip encrypts and decrypts a symmetric key", async () => { + cryptoFunctionService.aesEncrypt.mockResolvedValue(makeStaticByteArray(64, 0)); + cryptoFunctionService.randomBytes.mockResolvedValue(makeStaticByteArray(16) as CsprngArray); + cryptoFunctionService.hmac.mockResolvedValue(makeStaticByteArray(32)); + + const key = new SymmetricCryptoKey(makeStaticByteArray(64)); + const wrappingKey = new SymmetricCryptoKey(makeStaticByteArray(64)); + const encString = await encryptService.wrapSymmetricKey(key, wrappingKey); + expect(encString.encryptionType).toEqual(EncryptionType.AesCbc256_HmacSha256_B64); + expect(encString.data).toEqual(Utils.fromBufferToB64(makeStaticByteArray(64, 0))); + }); + it("fails if key toBeWrapped is null", async () => { + const wrappingKey = new SymmetricCryptoKey(makeStaticByteArray(64)); + await expect(encryptService.wrapSymmetricKey(null, wrappingKey)).rejects.toThrow( + "No keyToBeWrapped provided for wrapping.", + ); + }); + it("fails if wrapping key is null", async () => { + const key = new SymmetricCryptoKey(makeStaticByteArray(64)); + await expect(encryptService.wrapSymmetricKey(key, null)).rejects.toThrow( + "No wrappingKey provided for wrapping.", + ); + }); + it("fails if type 0 key is provided with flag turned on", async () => { + (encryptService as any).blockType0 = true; + const mock32Key = mock(); + mock32Key.inner.mockReturnValue({ + type: 0, + encryptionKey: makeStaticByteArray(32), + }); + + await expect(encryptService.wrapSymmetricKey(mock32Key, mock32Key)).rejects.toThrow( + "Type 0 encryption is not supported.", + ); + }); + }); + + describe("wrapDecapsulationKey", () => { + it("roundtrip encrypts and decrypts a decapsulation key", async () => { + cryptoFunctionService.aesEncrypt.mockResolvedValue(makeStaticByteArray(64, 0)); + cryptoFunctionService.randomBytes.mockResolvedValue(makeStaticByteArray(16) as CsprngArray); + cryptoFunctionService.hmac.mockResolvedValue(makeStaticByteArray(32)); + + const wrappingKey = new SymmetricCryptoKey(makeStaticByteArray(64)); + const encString = await encryptService.wrapDecapsulationKey( + makeStaticByteArray(64), + wrappingKey, + ); + expect(encString.encryptionType).toEqual(EncryptionType.AesCbc256_HmacSha256_B64); + expect(encString.data).toEqual(Utils.fromBufferToB64(makeStaticByteArray(64, 0))); + }); + it("fails if decapsulation key is null", async () => { + const wrappingKey = new SymmetricCryptoKey(makeStaticByteArray(64)); + await expect(encryptService.wrapDecapsulationKey(null, wrappingKey)).rejects.toThrow( + "No decapsulation key provided for wrapping.", + ); + }); + it("fails if wrapping key is null", async () => { + const decapsulationKey = makeStaticByteArray(64); + await expect(encryptService.wrapDecapsulationKey(decapsulationKey, null)).rejects.toThrow( + "No wrappingKey provided for wrapping.", + ); + }); + it("throws if type 0 key is provided with flag turned on", async () => { + (encryptService as any).blockType0 = true; + const mock32Key = mock(); + mock32Key.inner.mockReturnValue({ + type: 0, + encryptionKey: makeStaticByteArray(32), + }); + + await expect( + encryptService.wrapDecapsulationKey(new Uint8Array(200), mock32Key), + ).rejects.toThrow("Type 0 encryption is not supported."); + }); + }); + + describe("wrapEncapsulationKey", () => { + it("roundtrip encrypts and decrypts an encapsulationKey key", async () => { + cryptoFunctionService.aesEncrypt.mockResolvedValue(makeStaticByteArray(64, 0)); + cryptoFunctionService.randomBytes.mockResolvedValue(makeStaticByteArray(16) as CsprngArray); + cryptoFunctionService.hmac.mockResolvedValue(makeStaticByteArray(32)); + + const wrappingKey = new SymmetricCryptoKey(makeStaticByteArray(64)); + const encString = await encryptService.wrapEncapsulationKey( + makeStaticByteArray(64), + wrappingKey, + ); + expect(encString.encryptionType).toEqual(EncryptionType.AesCbc256_HmacSha256_B64); + expect(encString.data).toEqual(Utils.fromBufferToB64(makeStaticByteArray(64, 0))); + }); + it("fails if encapsulation key is null", async () => { + const wrappingKey = new SymmetricCryptoKey(makeStaticByteArray(64)); + await expect(encryptService.wrapEncapsulationKey(null, wrappingKey)).rejects.toThrow( + "No encapsulation key provided for wrapping.", + ); + }); + it("fails if wrapping key is null", async () => { + const encapsulationKey = makeStaticByteArray(64); + await expect(encryptService.wrapEncapsulationKey(encapsulationKey, null)).rejects.toThrow( + "No wrappingKey provided for wrapping.", + ); + }); + it("throws if type 0 key is provided with flag turned on", async () => { + (encryptService as any).blockType0 = true; + const mock32Key = mock(); + mock32Key.inner.mockReturnValue({ + type: 0, + encryptionKey: makeStaticByteArray(32), + }); + + await expect( + encryptService.wrapEncapsulationKey(new Uint8Array(200), mock32Key), + ).rejects.toThrow("Type 0 encryption is not supported."); + }); + }); + describe("onServerConfigChange", () => { const newConfig = mock(); @@ -63,7 +184,10 @@ describe("EncryptService", () => { (encryptService as any).blockType0 = true; const key = new SymmetricCryptoKey(makeStaticByteArray(32)); const mock32Key = mock(); - mock32Key.key = makeStaticByteArray(32); + mock32Key.inner.mockReturnValue({ + type: 0, + encryptionKey: makeStaticByteArray(32), + }); await expect(encryptService.encrypt(null!, key)).rejects.toThrow( "Type 0 encryption is not supported.", @@ -145,7 +269,10 @@ describe("EncryptService", () => { (encryptService as any).blockType0 = true; const key = new SymmetricCryptoKey(makeStaticByteArray(32)); const mock32Key = mock(); - mock32Key.key = makeStaticByteArray(32); + mock32Key.inner.mockReturnValue({ + type: 0, + encryptionKey: makeStaticByteArray(32), + }); await expect(encryptService.encryptToBytes(plainValue, key)).rejects.toThrow( "Type 0 encryption is not supported.", @@ -228,7 +355,7 @@ describe("EncryptService", () => { expect(cryptoFunctionService.aesDecrypt).toBeCalledWith( expect.toEqualBuffer(encBuffer.dataBytes), expect.toEqualBuffer(encBuffer.ivBytes), - expect.toEqualBuffer(key.encKey), + expect.toEqualBuffer(key.inner().encryptionKey), "cbc", ); @@ -249,7 +376,7 @@ describe("EncryptService", () => { expect(cryptoFunctionService.aesDecrypt).toBeCalledWith( expect.toEqualBuffer(encBuffer.dataBytes), expect.toEqualBuffer(encBuffer.ivBytes), - expect.toEqualBuffer(key.encKey), + expect.toEqualBuffer(key.inner().encryptionKey), "cbc", ); @@ -267,7 +394,7 @@ describe("EncryptService", () => { expect(cryptoFunctionService.hmac).toBeCalledWith( expect.toEqualBuffer(expectedMacData), - key.macKey, + (key.inner() as Aes256CbcHmacKey).authenticationKey, "sha256", ); @@ -411,6 +538,98 @@ describe("EncryptService", () => { }); }); + describe("encryptString", () => { + it("is a proxy to encrypt", async () => { + const key = new SymmetricCryptoKey(makeStaticByteArray(64)); + const plainValue = "data"; + encryptService.encrypt = jest.fn(); + await encryptService.encryptString(plainValue, key); + expect(encryptService.encrypt).toHaveBeenCalledWith(plainValue, key); + }); + }); + + describe("encryptBytes", () => { + it("is a proxy to encrypt", async () => { + const key = new SymmetricCryptoKey(makeStaticByteArray(64)); + const plainValue = makeStaticByteArray(16, 1); + encryptService.encrypt = jest.fn(); + await encryptService.encryptBytes(plainValue, key); + expect(encryptService.encrypt).toHaveBeenCalledWith(plainValue, key); + }); + }); + + describe("encryptFileData", () => { + it("is a proxy to encryptToBytes", async () => { + const key = new SymmetricCryptoKey(makeStaticByteArray(64)); + const plainValue = makeStaticByteArray(16, 1); + encryptService.encryptToBytes = jest.fn(); + await encryptService.encryptFileData(plainValue, key); + expect(encryptService.encryptToBytes).toHaveBeenCalledWith(plainValue, key); + }); + }); + + describe("decryptString", () => { + it("is a proxy to decryptToUtf8", async () => { + const key = new SymmetricCryptoKey(makeStaticByteArray(64)); + const encString = new EncString(EncryptionType.AesCbc256_B64, "data"); + encryptService.decryptToUtf8 = jest.fn(); + await encryptService.decryptString(encString, key); + expect(encryptService.decryptToUtf8).toHaveBeenCalledWith(encString, key); + }); + }); + + describe("decryptBytes", () => { + it("is a proxy to decryptToBytes", async () => { + const key = new SymmetricCryptoKey(makeStaticByteArray(64)); + const encString = new EncString(EncryptionType.AesCbc256_B64, "data"); + encryptService.decryptToBytes = jest.fn(); + await encryptService.decryptBytes(encString, key); + expect(encryptService.decryptToBytes).toHaveBeenCalledWith(encString, key); + }); + }); + + describe("decryptFileData", () => { + it("is a proxy to decrypt", async () => { + const key = new SymmetricCryptoKey(makeStaticByteArray(64)); + const encString = new EncArrayBuffer(makeStaticByteArray(60, EncryptionType.AesCbc256_B64)); + encryptService.decryptToBytes = jest.fn(); + await encryptService.decryptFileData(encString, key); + expect(encryptService.decryptToBytes).toHaveBeenCalledWith(encString, key); + }); + }); + + describe("unwrapDecapsulationKey", () => { + it("is a proxy to decryptBytes", async () => { + const key = new SymmetricCryptoKey(makeStaticByteArray(64)); + const encString = new EncString(EncryptionType.AesCbc256_B64, "data"); + encryptService.decryptBytes = jest.fn(); + await encryptService.unwrapDecapsulationKey(encString, key); + expect(encryptService.decryptBytes).toHaveBeenCalledWith(encString, key); + }); + }); + + describe("unwrapEncapsulationKey", () => { + it("is a proxy to decryptBytes", async () => { + const key = new SymmetricCryptoKey(makeStaticByteArray(64)); + const encString = new EncString(EncryptionType.AesCbc256_B64, "data"); + encryptService.decryptBytes = jest.fn(); + await encryptService.unwrapEncapsulationKey(encString, key); + expect(encryptService.decryptBytes).toHaveBeenCalledWith(encString, key); + }); + }); + + describe("unwrapSymmetricKey", () => { + it("is a proxy to decryptBytes", async () => { + const key = new SymmetricCryptoKey(makeStaticByteArray(64)); + const encString = new EncString(EncryptionType.AesCbc256_B64, "data"); + const jestFn = jest.fn(); + jestFn.mockResolvedValue(new Uint8Array(64)); + encryptService.decryptBytes = jestFn; + await encryptService.unwrapSymmetricKey(encString, key); + expect(encryptService.decryptBytes).toHaveBeenCalledWith(encString, key); + }); + }); + describe("rsa", () => { const data = makeStaticByteArray(64, 100); const testKey = new SymmetricCryptoKey(data); @@ -442,7 +661,7 @@ describe("EncryptService", () => { const actual = await encryptService.encapsulateKeyUnsigned(testKey, publicKey); expect(cryptoFunctionService.rsaEncrypt).toBeCalledWith( - expect.toEqualBuffer(testKey.key), + expect.toEqualBuffer(testKey.toEncoded()), expect.toEqualBuffer(publicKey), "sha1", ); @@ -450,6 +669,12 @@ describe("EncryptService", () => { expect(actual).toEqual(encString); expect(actual.dataBytes).toEqualBuffer(encryptedData); }); + + it("throws if no data was provided", () => { + return expect(encryptService.rsaEncrypt(null, new Uint8Array(32))).rejects.toThrow( + "No data provided for encryption", + ); + }); }); describe("decapsulateKeyUnsigned", () => { @@ -487,7 +712,7 @@ describe("EncryptService", () => { "sha1", ); - expect(actual.key).toEqualBuffer(data); + expect(actual.toEncoded()).toEqualBuffer(data); }); }); }); diff --git a/libs/common/src/key-management/crypto/services/web-crypto-function.service.ts b/libs/common/src/key-management/crypto/services/web-crypto-function.service.ts index 0c80d508b2d..430774ca2ed 100644 --- a/libs/common/src/key-management/crypto/services/web-crypto-function.service.ts +++ b/libs/common/src/key-management/crypto/services/web-crypto-function.service.ts @@ -1,6 +1,7 @@ import * as argon2 from "argon2-browser"; import * as forge from "node-forge"; +import { EncryptionType } from "../../../platform/enums"; import { Utils } from "../../../platform/misc/utils"; import { CbcDecryptParameters, @@ -247,37 +248,26 @@ export class WebCryptoFunctionService implements CryptoFunctionService { mac: string | null, key: SymmetricCryptoKey, ): CbcDecryptParameters { - const p = {} as CbcDecryptParameters; - if (key.meta != null) { - p.encKey = key.meta.encKeyByteString; - p.macKey = key.meta.macKeyByteString; + const innerKey = key.inner(); + if (innerKey.type === EncryptionType.AesCbc256_B64) { + return { + iv: forge.util.decode64(iv), + data: forge.util.decode64(data), + encKey: forge.util.createBuffer(innerKey.encryptionKey).getBytes(), + } as CbcDecryptParameters; + } else if (innerKey.type === EncryptionType.AesCbc256_HmacSha256_B64) { + const macData = forge.util.decode64(iv) + forge.util.decode64(data); + return { + iv: forge.util.decode64(iv), + data: forge.util.decode64(data), + encKey: forge.util.createBuffer(innerKey.encryptionKey).getBytes(), + macKey: forge.util.createBuffer(innerKey.authenticationKey).getBytes(), + mac: forge.util.decode64(mac!), + macData, + } as CbcDecryptParameters; + } else { + throw new Error("Unsupported encryption type."); } - - if (p.encKey == null) { - p.encKey = forge.util.decode64(key.encKeyB64); - } - p.data = forge.util.decode64(data); - p.iv = forge.util.decode64(iv); - p.macData = p.iv + p.data; - if (p.macKey == null && key.macKeyB64 != null) { - p.macKey = forge.util.decode64(key.macKeyB64); - } - if (mac != null) { - p.mac = forge.util.decode64(mac); - } - - // cache byte string keys for later - if (key.meta == null) { - key.meta = {}; - } - if (key.meta.encKeyByteString == null) { - key.meta.encKeyByteString = p.encKey; - } - if (p.macKey != null && key.meta.macKeyByteString == null) { - key.meta.macKeyByteString = p.macKey; - } - - return p; } aesDecryptFast({ diff --git a/libs/common/src/key-management/device-trust/services/device-trust.service.implementation.ts b/libs/common/src/key-management/device-trust/services/device-trust.service.implementation.ts index a2211753f4e..06999cab9c3 100644 --- a/libs/common/src/key-management/device-trust/services/device-trust.service.implementation.ts +++ b/libs/common/src/key-management/device-trust/services/device-trust.service.implementation.ts @@ -164,10 +164,10 @@ export class DeviceTrustService implements DeviceTrustServiceAbstraction { this.encryptService.encapsulateKeyUnsigned(userKey, devicePublicKey), // Encrypt devicePublicKey with user key - this.encryptService.encrypt(devicePublicKey, userKey), + this.encryptService.wrapEncapsulationKey(devicePublicKey, userKey), // Encrypt devicePrivateKey with deviceKey - this.encryptService.encrypt(devicePrivateKey, deviceKey), + this.encryptService.wrapDecapsulationKey(devicePrivateKey, deviceKey), ]); // Send encrypted keys to server @@ -209,9 +209,8 @@ export class DeviceTrustService implements DeviceTrustServiceAbstraction { devices.data .filter((device) => device.isTrusted) .map(async (device) => { - const deviceWithKeys = await this.devicesApiService.getDeviceKeys(device.identifier); const publicKey = await this.encryptService.decryptToBytes( - deviceWithKeys.encryptedPublicKey, + new EncString(device.encryptedPublicKey), oldUserKey, ); @@ -222,8 +221,8 @@ export class DeviceTrustService implements DeviceTrustServiceAbstraction { } const newEncryptedPublicKey = await this.encryptService.encrypt(publicKey, newUserKey); - const newEncryptedUserKey = await this.encryptService.rsaEncrypt( - newUserKey.key, + const newEncryptedUserKey = await this.encryptService.encapsulateKeyUnsigned( + newUserKey, publicKey, ); @@ -291,7 +290,7 @@ export class DeviceTrustService implements DeviceTrustServiceAbstraction { ); // Re-encrypt the device public key with the new user key - const encryptedDevicePublicKey = await this.encryptService.encrypt( + const encryptedDevicePublicKey = await this.encryptService.wrapEncapsulationKey( decryptedDevicePublicKey, newUserKey, ); diff --git a/libs/common/src/key-management/device-trust/services/device-trust.service.spec.ts b/libs/common/src/key-management/device-trust/services/device-trust.service.spec.ts index 8431fe4cc35..7d4a25c1f08 100644 --- a/libs/common/src/key-management/device-trust/services/device-trust.service.spec.ts +++ b/libs/common/src/key-management/device-trust/services/device-trust.service.spec.ts @@ -346,8 +346,6 @@ describe("deviceTrustService", () => { const deviceRsaKeyLength = 2048; let mockDeviceRsaKeyPair: [Uint8Array, Uint8Array]; - let mockDevicePrivateKey: Uint8Array; - let mockDevicePublicKey: Uint8Array; let mockDevicePublicKeyEncryptedUserKey: EncString; let mockUserKeyEncryptedDevicePublicKey: EncString; let mockDeviceKeyEncryptedDevicePrivateKey: EncString; @@ -366,7 +364,8 @@ describe("deviceTrustService", () => { let rsaGenerateKeyPairSpy: jest.SpyInstance; let cryptoSvcGetUserKeySpy: jest.SpyInstance; let cryptoSvcRsaEncryptSpy: jest.SpyInstance; - let encryptServiceEncryptSpy: jest.SpyInstance; + let encryptServiceWrapDecapsulationKeySpy: jest.SpyInstance; + let encryptServiceWrapEncapsulationKeySpy: jest.SpyInstance; let appIdServiceGetAppIdSpy: jest.SpyInstance; let devicesApiServiceUpdateTrustedDeviceKeysSpy: jest.SpyInstance; @@ -384,9 +383,6 @@ describe("deviceTrustService", () => { new Uint8Array(deviceRsaKeyLength), ]; - mockDevicePublicKey = mockDeviceRsaKeyPair[0]; - mockDevicePrivateKey = mockDeviceRsaKeyPair[1]; - mockDevicePublicKeyEncryptedUserKey = new EncString( EncryptionType.Rsa2048_OaepSha1_B64, "mockDevicePublicKeyEncryptedUserKey", @@ -419,13 +415,17 @@ describe("deviceTrustService", () => { .spyOn(encryptService, "encapsulateKeyUnsigned") .mockResolvedValue(mockDevicePublicKeyEncryptedUserKey); - encryptServiceEncryptSpy = jest - .spyOn(encryptService, "encrypt") + encryptServiceWrapEncapsulationKeySpy = jest + .spyOn(encryptService, "wrapEncapsulationKey") .mockImplementation((plainValue, key) => { - if (plainValue === mockDevicePublicKey && key === mockUserKey) { + if (plainValue instanceof Uint8Array && key instanceof SymmetricCryptoKey) { return Promise.resolve(mockUserKeyEncryptedDevicePublicKey); } - if (plainValue === mockDevicePrivateKey && key === mockDeviceKey) { + }); + encryptServiceWrapDecapsulationKeySpy = jest + .spyOn(encryptService, "wrapDecapsulationKey") + .mockImplementation((plainValue, key) => { + if (plainValue instanceof Uint8Array && key instanceof SymmetricCryptoKey) { return Promise.resolve(mockDeviceKeyEncryptedDevicePrivateKey); } }); @@ -450,9 +450,10 @@ describe("deviceTrustService", () => { // RsaEncrypt must be called w/ a user key array buffer of 64 bytes const userKey = cryptoSvcRsaEncryptSpy.mock.calls[0][0]; - expect(userKey.key.byteLength).toBe(64); + expect(userKey.inner().type).toBe(EncryptionType.AesCbc256_HmacSha256_B64); - expect(encryptServiceEncryptSpy).toHaveBeenCalledTimes(2); + expect(encryptServiceWrapDecapsulationKeySpy).toHaveBeenCalledTimes(1); + expect(encryptServiceWrapEncapsulationKeySpy).toHaveBeenCalledTimes(1); expect(appIdServiceGetAppIdSpy).toHaveBeenCalledTimes(1); expect(devicesApiServiceUpdateTrustedDeviceKeysSpy).toHaveBeenCalledTimes(1); @@ -508,9 +509,14 @@ describe("deviceTrustService", () => { errorText: "rsaEncrypt error", }, { - method: "encryptService.encrypt", - spy: () => encryptServiceEncryptSpy, - errorText: "encryptService.encrypt error", + method: "encryptService.wrapEncapsulationKey", + spy: () => encryptServiceWrapEncapsulationKeySpy, + errorText: "encryptService.wrapEncapsulationKey error", + }, + { + method: "encryptService.wrapDecapsulationKey", + spy: () => encryptServiceWrapDecapsulationKeySpy, + errorText: "encryptService.wrapDecapsulationKey error", }, ]; @@ -700,7 +706,9 @@ describe("deviceTrustService", () => { ); encryptService.decryptToBytes.mockResolvedValue(null); encryptService.encrypt.mockResolvedValue(new EncString("test_encrypted_data")); - encryptService.rsaEncrypt.mockResolvedValue(new EncString("test_encrypted_data")); + encryptService.encapsulateKeyUnsigned.mockResolvedValue( + new EncString("test_encrypted_data"), + ); const protectedDeviceResponse = new ProtectedDeviceResponse({ id: "id", @@ -855,8 +863,8 @@ describe("deviceTrustService", () => { // Mock the decryption of the public key with the old user key encryptService.decryptToBytes.mockImplementationOnce((_encValue, privateKeyValue) => { - expect(privateKeyValue.key.byteLength).toBe(64); - expect(new Uint8Array(privateKeyValue.key)[0]).toBe(FakeOldUserKeyMarker); + expect(privateKeyValue.inner().type).toBe(EncryptionType.AesCbc256_HmacSha256_B64); + expect(new Uint8Array(privateKeyValue.toEncoded())[0]).toBe(FakeOldUserKeyMarker); const data = new Uint8Array(250); data.fill(FakeDecryptedPublicKeyMarker, 0, 1); return Promise.resolve(data); @@ -864,19 +872,19 @@ describe("deviceTrustService", () => { // Mock the encryption of the new user key with the decrypted public key encryptService.encapsulateKeyUnsigned.mockImplementationOnce((data, publicKey) => { - expect(data.key.byteLength).toBe(64); // New key should also be 64 bytes - expect(new Uint8Array(data.key)[0]).toBe(FakeNewUserKeyMarker); // New key should have the first byte be '1'; + expect(data.inner().type).toBe(EncryptionType.AesCbc256_HmacSha256_B64); // New key should also be 64 bytes + expect(new Uint8Array(data.toEncoded())[0]).toBe(FakeNewUserKeyMarker); // New key should have the first byte be '1'; expect(new Uint8Array(publicKey)[0]).toBe(FakeDecryptedPublicKeyMarker); return Promise.resolve(new EncString("4.ZW5jcnlwdGVkdXNlcg==")); }); // Mock the reencryption of the device public key with the new user key - encryptService.encrypt.mockImplementationOnce((plainValue, key) => { + encryptService.wrapEncapsulationKey.mockImplementationOnce((plainValue, key) => { expect(plainValue).toBeInstanceOf(Uint8Array); expect(new Uint8Array(plainValue as Uint8Array)[0]).toBe(FakeDecryptedPublicKeyMarker); - expect(new Uint8Array(key.key)[0]).toBe(FakeNewUserKeyMarker); + expect(new Uint8Array(key.toEncoded())[0]).toBe(FakeNewUserKeyMarker); return Promise.resolve( new EncString("2.ZW5jcnlwdGVkcHVibGlj|ZW5jcnlwdGVkcHVibGlj|ZW5jcnlwdGVkcHVibGlj"), ); diff --git a/libs/common/src/key-management/key-connector/services/key-connector.service.spec.ts b/libs/common/src/key-management/key-connector/services/key-connector.service.spec.ts index fd3ce0c4777..b88ada56129 100644 --- a/libs/common/src/key-management/key-connector/services/key-connector.service.spec.ts +++ b/libs/common/src/key-management/key-connector/services/key-connector.service.spec.ts @@ -252,7 +252,9 @@ describe("KeyConnectorService", () => { const organization = organizationData(true, true, "https://key-connector-url.com", 2, false); const masterKey = getMockMasterKey(); masterPasswordService.masterKeySubject.next(masterKey); - const keyConnectorRequest = new KeyConnectorUserKeyRequest(masterKey.encKeyB64); + const keyConnectorRequest = new KeyConnectorUserKeyRequest( + Utils.fromBufferToB64(masterKey.inner().encryptionKey), + ); jest.spyOn(keyConnectorService, "getManagingOrganization").mockResolvedValue(organization); jest.spyOn(apiService, "postUserKeyToKeyConnector").mockResolvedValue(); @@ -273,7 +275,9 @@ describe("KeyConnectorService", () => { // Arrange const organization = organizationData(true, true, "https://key-connector-url.com", 2, false); const masterKey = getMockMasterKey(); - const keyConnectorRequest = new KeyConnectorUserKeyRequest(masterKey.encKeyB64); + const keyConnectorRequest = new KeyConnectorUserKeyRequest( + Utils.fromBufferToB64(masterKey.inner().encryptionKey), + ); const error = new Error("Failed to post user key to key connector"); organizationService.organizations$.mockReturnValue(of([organization])); diff --git a/libs/common/src/key-management/key-connector/services/key-connector.service.ts b/libs/common/src/key-management/key-connector/services/key-connector.service.ts index 91b8e9100ac..9799f06f64a 100644 --- a/libs/common/src/key-management/key-connector/services/key-connector.service.ts +++ b/libs/common/src/key-management/key-connector/services/key-connector.service.ts @@ -95,7 +95,9 @@ export class KeyConnectorService implements KeyConnectorServiceAbstraction { userId ??= (await firstValueFrom(this.accountService.activeAccount$))?.id; const organization = await this.getManagingOrganization(userId); const masterKey = await firstValueFrom(this.masterPasswordService.masterKey$(userId)); - const keyConnectorRequest = new KeyConnectorUserKeyRequest(masterKey.encKeyB64); + const keyConnectorRequest = new KeyConnectorUserKeyRequest( + Utils.fromBufferToB64(masterKey.inner().encryptionKey), + ); try { await this.apiService.postUserKeyToKeyConnector( @@ -157,7 +159,9 @@ export class KeyConnectorService implements KeyConnectorServiceAbstraction { await this.tokenService.getEmail(), kdfConfig, ); - const keyConnectorRequest = new KeyConnectorUserKeyRequest(masterKey.encKeyB64); + const keyConnectorRequest = new KeyConnectorUserKeyRequest( + Utils.fromBufferToB64(masterKey.inner().encryptionKey), + ); await this.masterPasswordService.setMasterKey(masterKey, userId); const userKey = await this.keyService.makeUserKey(masterKey); diff --git a/libs/common/src/models/response/notification.response.ts b/libs/common/src/models/response/notification.response.ts index aa0ecc97b58..d1bf96b1956 100644 --- a/libs/common/src/models/response/notification.response.ts +++ b/libs/common/src/models/response/notification.response.ts @@ -1,3 +1,5 @@ +import { NotificationViewResponse as EndUserNotificationResponse } from "@bitwarden/common/vault/notifications/models"; + import { NotificationType } from "../../enums"; import { BaseResponse } from "./base.response"; @@ -57,6 +59,10 @@ export class NotificationResponse extends BaseResponse { case NotificationType.SyncOrganizationCollectionSettingChanged: this.payload = new OrganizationCollectionSettingChangedPushNotification(payload); break; + case NotificationType.Notification: + case NotificationType.NotificationStatus: + this.payload = new EndUserNotificationResponse(payload); + break; default: break; } diff --git a/libs/common/src/platform/models/domain/symmetric-crypto-key.spec.ts b/libs/common/src/platform/models/domain/symmetric-crypto-key.spec.ts index cce99b847bb..9246652b4c8 100644 --- a/libs/common/src/platform/models/domain/symmetric-crypto-key.spec.ts +++ b/libs/common/src/platform/models/domain/symmetric-crypto-key.spec.ts @@ -2,7 +2,7 @@ import { makeStaticByteArray } from "../../../../spec"; import { EncryptionType } from "../../enums"; import { Utils } from "../../misc/utils"; -import { SymmetricCryptoKey } from "./symmetric-crypto-key"; +import { Aes256CbcHmacKey, SymmetricCryptoKey } from "./symmetric-crypto-key"; describe("SymmetricCryptoKey", () => { it("errors if no key", () => { @@ -19,13 +19,7 @@ describe("SymmetricCryptoKey", () => { const cryptoKey = new SymmetricCryptoKey(key); expect(cryptoKey).toEqual({ - encKey: key, - encKeyB64: "AAECAwQFBgcICQoLDA0ODxAREhMUFRYXGBkaGxwdHh8=", - encType: EncryptionType.AesCbc256_B64, - key: key, keyB64: "AAECAwQFBgcICQoLDA0ODxAREhMUFRYXGBkaGxwdHh8=", - macKey: null, - macKeyB64: undefined, innerKey: { type: EncryptionType.AesCbc256_B64, encryptionKey: key, @@ -38,14 +32,8 @@ describe("SymmetricCryptoKey", () => { const cryptoKey = new SymmetricCryptoKey(key); expect(cryptoKey).toEqual({ - encKey: key.slice(0, 32), - encKeyB64: "AAECAwQFBgcICQoLDA0ODxAREhMUFRYXGBkaGxwdHh8=", - encType: EncryptionType.AesCbc256_HmacSha256_B64, - key: key, keyB64: "AAECAwQFBgcICQoLDA0ODxAREhMUFRYXGBkaGxwdHh8gISIjJCUmJygpKissLS4vMDEyMzQ1Njc4OTo7PD0+Pw==", - macKey: key.slice(32, 64), - macKeyB64: "ICEiIyQlJicoKSorLC0uLzAxMjM0NTY3ODk6Ozw9Pj8=", innerKey: { type: EncryptionType.AesCbc256_HmacSha256_B64, encryptionKey: key.slice(0, 32), @@ -86,8 +74,8 @@ describe("SymmetricCryptoKey", () => { expect(actual).toEqual({ type: EncryptionType.AesCbc256_HmacSha256_B64, - encryptionKey: key.encKey, - authenticationKey: key.macKey, + encryptionKey: key.inner().encryptionKey, + authenticationKey: (key.inner() as Aes256CbcHmacKey).authenticationKey, }); }); @@ -95,7 +83,7 @@ describe("SymmetricCryptoKey", () => { const key = new SymmetricCryptoKey(makeStaticByteArray(32)); const actual = key.toEncoded(); - expect(actual).toEqual(key.encKey); + expect(actual).toEqual(key.inner().encryptionKey); }); it("toEncoded returns encoded key for AesCbc256_HmacSha256_B64", () => { diff --git a/libs/common/src/platform/models/domain/symmetric-crypto-key.ts b/libs/common/src/platform/models/domain/symmetric-crypto-key.ts index 45e15c1f602..ad16ddd06f6 100644 --- a/libs/common/src/platform/models/domain/symmetric-crypto-key.ts +++ b/libs/common/src/platform/models/domain/symmetric-crypto-key.ts @@ -24,16 +24,7 @@ export type Aes256CbcKey = { export class SymmetricCryptoKey { private innerKey: Aes256CbcHmacKey | Aes256CbcKey; - key: Uint8Array; - encKey: Uint8Array; - macKey?: Uint8Array; - encType: EncryptionType; - keyB64: string; - encKeyB64: string; - macKeyB64: string; - - meta: any; /** * @param key The key in one of the permitted serialization formats @@ -48,30 +39,14 @@ export class SymmetricCryptoKey { type: EncryptionType.AesCbc256_B64, encryptionKey: key, }; - this.encType = EncryptionType.AesCbc256_B64; - this.key = key; - this.keyB64 = Utils.fromBufferToB64(this.key); - - this.encKey = key; - this.encKeyB64 = Utils.fromBufferToB64(this.encKey); - - this.macKey = null; - this.macKeyB64 = undefined; + this.keyB64 = this.toBase64(); } else if (key.byteLength === 64) { this.innerKey = { type: EncryptionType.AesCbc256_HmacSha256_B64, encryptionKey: key.slice(0, 32), authenticationKey: key.slice(32), }; - this.encType = EncryptionType.AesCbc256_HmacSha256_B64; - this.key = key; - this.keyB64 = Utils.fromBufferToB64(this.key); - - this.encKey = key.slice(0, 32); - this.encKeyB64 = Utils.fromBufferToB64(this.encKey); - - this.macKey = key.slice(32); - this.macKeyB64 = Utils.fromBufferToB64(this.macKey); + this.keyB64 = this.toBase64(); } else { throw new Error(`Unsupported encType/key length ${key.byteLength}`); } diff --git a/libs/common/src/platform/services/key-generation.service.spec.ts b/libs/common/src/platform/services/key-generation.service.spec.ts index a2597414d0e..f75eaeb25be 100644 --- a/libs/common/src/platform/services/key-generation.service.spec.ts +++ b/libs/common/src/platform/services/key-generation.service.spec.ts @@ -4,6 +4,7 @@ import { PBKDF2KdfConfig, Argon2KdfConfig } from "@bitwarden/key-management"; import { CryptoFunctionService } from "../../key-management/crypto/abstractions/crypto-function.service"; import { CsprngArray } from "../../types/csprng"; +import { EncryptionType } from "../enums"; import { KeyGenerationService } from "./key-generation.service"; @@ -52,7 +53,7 @@ describe("KeyGenerationService", () => { expect(salt).toEqual(inputSalt); expect(material).toEqual(inputMaterial); - expect(derivedKey.key.length).toEqual(64); + expect(derivedKey.inner().type).toEqual(EncryptionType.AesCbc256_HmacSha256_B64); }, ); }); @@ -67,7 +68,7 @@ describe("KeyGenerationService", () => { const key = await sut.deriveKeyFromMaterial(material, salt, purpose); - expect(key.key.length).toEqual(64); + expect(key.inner().type).toEqual(EncryptionType.AesCbc256_HmacSha256_B64); }); }); @@ -81,7 +82,7 @@ describe("KeyGenerationService", () => { const key = await sut.deriveKeyFromPassword(password, salt, kdfConfig); - expect(key.key.length).toEqual(32); + expect(key.inner().type).toEqual(EncryptionType.AesCbc256_B64); }); it("should derive a 32 byte key from a password using argon2id", async () => { @@ -94,7 +95,7 @@ describe("KeyGenerationService", () => { const key = await sut.deriveKeyFromPassword(password, salt, kdfConfig); - expect(key.key.length).toEqual(32); + expect(key.inner().type).toEqual(EncryptionType.AesCbc256_B64); }); }); }); diff --git a/libs/common/src/platform/services/key-generation.service.ts b/libs/common/src/platform/services/key-generation.service.ts index 6203eaabdd1..8f9e6856aa0 100644 --- a/libs/common/src/platform/services/key-generation.service.ts +++ b/libs/common/src/platform/services/key-generation.service.ts @@ -1,5 +1,6 @@ // FIXME: Update this file to be type safe and remove this and next line // @ts-strict-ignore +import { MasterKey, PinKey } from "@bitwarden/common/types/key"; import { KdfConfig, PBKDF2KdfConfig, Argon2KdfConfig, KdfType } from "@bitwarden/key-management"; import { CryptoFunctionService } from "../../key-management/crypto/abstractions/crypto-function.service"; @@ -78,10 +79,21 @@ export class KeyGenerationService implements KeyGenerationServiceAbstraction { return new SymmetricCryptoKey(key); } - async stretchKey(key: SymmetricCryptoKey): Promise { + async stretchKey(key: MasterKey | PinKey): Promise { const newKey = new Uint8Array(64); - const encKey = await this.cryptoFunctionService.hkdfExpand(key.key, "enc", 32, "sha256"); - const macKey = await this.cryptoFunctionService.hkdfExpand(key.key, "mac", 32, "sha256"); + // Master key and pin key are always 32 bytes + const encKey = await this.cryptoFunctionService.hkdfExpand( + key.inner().encryptionKey, + "enc", + 32, + "sha256", + ); + const macKey = await this.cryptoFunctionService.hkdfExpand( + key.inner().encryptionKey, + "mac", + 32, + "sha256", + ); newKey.set(new Uint8Array(encKey)); newKey.set(new Uint8Array(macKey), 32); diff --git a/libs/common/src/platform/state/state-definitions.ts b/libs/common/src/platform/state/state-definitions.ts index 70e0c3998dd..587212299df 100644 --- a/libs/common/src/platform/state/state-definitions.ts +++ b/libs/common/src/platform/state/state-definitions.ts @@ -206,7 +206,7 @@ export const VAULT_APPEARANCE = new StateDefinition("vaultAppearance", "disk"); export const SECURITY_TASKS_DISK = new StateDefinition("securityTasks", "disk"); export const AT_RISK_PASSWORDS_PAGE_DISK = new StateDefinition("atRiskPasswordsPage", "disk"); export const NOTIFICATION_DISK = new StateDefinition("notifications", "disk"); -export const VAULT_NUDGES_DISK = new StateDefinition("vaultNudges", "disk"); +export const VAULT_NUDGES_DISK = new StateDefinition("vaultNudges", "disk", { web: "disk-local" }); export const VAULT_BROWSER_INTRO_CAROUSEL = new StateDefinition( "vaultBrowserIntroCarousel", "disk", diff --git a/libs/common/src/platform/sync/core-sync.service.ts b/libs/common/src/platform/sync/core-sync.service.ts index 1865ffb852f..4020c75f764 100644 --- a/libs/common/src/platform/sync/core-sync.service.ts +++ b/libs/common/src/platform/sync/core-sync.service.ts @@ -28,6 +28,8 @@ import { StateService } from "../abstractions/state.service"; import { MessageSender } from "../messaging"; import { StateProvider, SYNC_DISK, UserKeyDefinition } from "../state"; +import { SyncOptions } from "./sync.service"; + const LAST_SYNC_DATE = new UserKeyDefinition(SYNC_DISK, "lastSync", { deserializer: (d) => (d != null ? new Date(d) : null), clearOn: ["logout"], @@ -55,6 +57,7 @@ export abstract class CoreSyncService implements SyncService { protected readonly stateProvider: StateProvider, ) {} + abstract fullSync(forceSync: boolean, syncOptions?: SyncOptions): Promise; abstract fullSync(forceSync: boolean, allowThrowOnError?: boolean): Promise; async getLastSync(): Promise { diff --git a/libs/common/src/platform/sync/default-sync.service.spec.ts b/libs/common/src/platform/sync/default-sync.service.spec.ts new file mode 100644 index 00000000000..ded06c8be6b --- /dev/null +++ b/libs/common/src/platform/sync/default-sync.service.spec.ts @@ -0,0 +1,199 @@ +import { mock, MockProxy } from "jest-mock-extended"; +import { of } from "rxjs"; + +import { CollectionService } from "@bitwarden/admin-console/common"; +import { + LogoutReason, + UserDecryptionOptions, + UserDecryptionOptionsServiceAbstraction, +} from "@bitwarden/auth/common"; +import { KeyService } from "@bitwarden/key-management"; + +import { Matrix } from "../../../spec/matrix"; +import { ApiService } from "../../abstractions/api.service"; +import { InternalOrganizationServiceAbstraction } from "../../admin-console/abstractions/organization/organization.service.abstraction"; +import { InternalPolicyService } from "../../admin-console/abstractions/policy/policy.service.abstraction"; +import { ProviderService } from "../../admin-console/abstractions/provider.service"; +import { Account, AccountService } from "../../auth/abstractions/account.service"; +import { AuthService } from "../../auth/abstractions/auth.service"; +import { AvatarService } from "../../auth/abstractions/avatar.service"; +import { TokenService } from "../../auth/abstractions/token.service"; +import { AuthenticationStatus } from "../../auth/enums/authentication-status"; +import { DomainSettingsService } from "../../autofill/services/domain-settings.service"; +import { BillingAccountProfileStateService } from "../../billing/abstractions"; +import { KeyConnectorService } from "../../key-management/key-connector/abstractions/key-connector.service"; +import { InternalMasterPasswordServiceAbstraction } from "../../key-management/master-password/abstractions/master-password.service.abstraction"; +import { SendApiService } from "../../tools/send/services/send-api.service.abstraction"; +import { InternalSendService } from "../../tools/send/services/send.service.abstraction"; +import { UserId } from "../../types/guid"; +import { CipherService } from "../../vault/abstractions/cipher.service"; +import { FolderApiServiceAbstraction } from "../../vault/abstractions/folder/folder-api.service.abstraction"; +import { InternalFolderService } from "../../vault/abstractions/folder/folder.service.abstraction"; +import { LogService } from "../abstractions/log.service"; +import { StateService } from "../abstractions/state.service"; +import { MessageSender } from "../messaging"; +import { StateProvider } from "../state"; + +import { DefaultSyncService } from "./default-sync.service"; +import { SyncResponse } from "./sync.response"; + +describe("DefaultSyncService", () => { + let masterPasswordAbstraction: MockProxy; + let accountService: MockProxy; + let apiService: MockProxy; + let domainSettingsService: MockProxy; + let folderService: MockProxy; + let cipherService: MockProxy; + let keyService: MockProxy; + let collectionService: MockProxy; + let messageSender: MockProxy; + let policyService: MockProxy; + let sendService: MockProxy; + let logService: MockProxy; + let keyConnectorService: MockProxy; + let stateService: MockProxy; + let providerService: MockProxy; + let folderApiService: MockProxy; + let organizationService: MockProxy; + let sendApiService: MockProxy; + let userDecryptionOptionsService: MockProxy; + let avatarService: MockProxy; + let logoutCallback: jest.Mock, [logoutReason: LogoutReason, userId?: UserId]>; + let billingAccountProfileStateService: MockProxy; + let tokenService: MockProxy; + let authService: MockProxy; + let stateProvider: MockProxy; + + let sut: DefaultSyncService; + + beforeEach(() => { + masterPasswordAbstraction = mock(); + accountService = mock(); + apiService = mock(); + domainSettingsService = mock(); + folderService = mock(); + cipherService = mock(); + keyService = mock(); + collectionService = mock(); + messageSender = mock(); + policyService = mock(); + sendService = mock(); + logService = mock(); + keyConnectorService = mock(); + stateService = mock(); + providerService = mock(); + folderApiService = mock(); + organizationService = mock(); + sendApiService = mock(); + userDecryptionOptionsService = mock(); + avatarService = mock(); + logoutCallback = jest.fn(); + billingAccountProfileStateService = mock(); + tokenService = mock(); + authService = mock(); + stateProvider = mock(); + + sut = new DefaultSyncService( + masterPasswordAbstraction, + accountService, + apiService, + domainSettingsService, + folderService, + cipherService, + keyService, + collectionService, + messageSender, + policyService, + sendService, + logService, + keyConnectorService, + stateService, + providerService, + folderApiService, + organizationService, + sendApiService, + userDecryptionOptionsService, + avatarService, + logoutCallback, + billingAccountProfileStateService, + tokenService, + authService, + stateProvider, + ); + }); + + const user1 = "user1" as UserId; + + describe("fullSync", () => { + beforeEach(() => { + accountService.activeAccount$ = of({ id: user1 } as Account); + Matrix.autoMockMethod(authService.authStatusFor$, () => of(AuthenticationStatus.Unlocked)); + apiService.getSync.mockResolvedValue( + new SyncResponse({ + profile: { + id: user1, + }, + folders: [], + collections: [], + ciphers: [], + sends: [], + domains: [], + policies: [], + }), + ); + Matrix.autoMockMethod(userDecryptionOptionsService.userDecryptionOptionsById$, () => + of({ hasMasterPassword: true } satisfies UserDecryptionOptions), + ); + stateProvider.getUser.mockReturnValue(mock()); + }); + + it("does a token refresh when option missing from options", async () => { + await sut.fullSync(true, { allowThrowOnError: false }); + + expect(apiService.refreshIdentityToken).toHaveBeenCalledTimes(1); + expect(apiService.getSync).toHaveBeenCalledTimes(1); + }); + + it("does a token refresh when boolean passed in", async () => { + await sut.fullSync(true, false); + + expect(apiService.refreshIdentityToken).toHaveBeenCalledTimes(1); + expect(apiService.getSync).toHaveBeenCalledTimes(1); + }); + + it("does a token refresh when skipTokenRefresh option passed in with false and allowThrowOnError also passed in", async () => { + await sut.fullSync(true, { allowThrowOnError: false, skipTokenRefresh: false }); + + expect(apiService.refreshIdentityToken).toHaveBeenCalledTimes(1); + expect(apiService.getSync).toHaveBeenCalledTimes(1); + }); + + it("does a token refresh when skipTokenRefresh option passed in with false by itself", async () => { + await sut.fullSync(true, { skipTokenRefresh: false }); + + expect(apiService.refreshIdentityToken).toHaveBeenCalledTimes(1); + expect(apiService.getSync).toHaveBeenCalledTimes(1); + }); + + it("does not do a token refresh when skipTokenRefresh passed in as true", async () => { + await sut.fullSync(true, { skipTokenRefresh: true }); + + expect(apiService.refreshIdentityToken).not.toHaveBeenCalled(); + expect(apiService.getSync).toHaveBeenCalledTimes(1); + }); + + it("does not do a token refresh when skipTokenRefresh passed in as true and allowThrowOnError also passed in", async () => { + await sut.fullSync(true, { allowThrowOnError: false, skipTokenRefresh: true }); + + expect(apiService.refreshIdentityToken).not.toHaveBeenCalled(); + expect(apiService.getSync).toHaveBeenCalledTimes(1); + }); + + it("does a token refresh when nothing passed in", async () => { + await sut.fullSync(true); + + expect(apiService.refreshIdentityToken).toHaveBeenCalledTimes(1); + expect(apiService.getSync).toHaveBeenCalledTimes(1); + }); + }); +}); diff --git a/libs/common/src/platform/sync/default-sync.service.ts b/libs/common/src/platform/sync/default-sync.service.ts index a6b1b974645..faf54f11912 100644 --- a/libs/common/src/platform/sync/default-sync.service.ts +++ b/libs/common/src/platform/sync/default-sync.service.ts @@ -54,6 +54,7 @@ import { MessageSender } from "../messaging"; import { StateProvider } from "../state"; import { CoreSyncService } from "./core-sync.service"; +import { SyncOptions } from "./sync.service"; export class DefaultSyncService extends CoreSyncService { syncInProgress = false; @@ -102,7 +103,15 @@ export class DefaultSyncService extends CoreSyncService { ); } - override async fullSync(forceSync: boolean, allowThrowOnError = false): Promise { + override async fullSync( + forceSync: boolean, + allowThrowOnErrorOrOptions?: boolean | SyncOptions, + ): Promise { + const { allowThrowOnError = false, skipTokenRefresh = false } = + typeof allowThrowOnErrorOrOptions === "boolean" + ? { allowThrowOnError: allowThrowOnErrorOrOptions } + : (allowThrowOnErrorOrOptions ?? {}); + const userId = await firstValueFrom(this.accountService.activeAccount$.pipe(map((a) => a?.id))); this.syncStarted(); const authStatus = await firstValueFrom(this.authService.authStatusFor$(userId)); @@ -127,7 +136,9 @@ export class DefaultSyncService extends CoreSyncService { } try { - await this.apiService.refreshIdentityToken(); + if (!skipTokenRefresh) { + await this.apiService.refreshIdentityToken(); + } const response = await this.apiService.getSync(); await this.syncProfile(response.profile); diff --git a/libs/common/src/platform/sync/sync.service.ts b/libs/common/src/platform/sync/sync.service.ts index 967e4db27a5..6ef62fc9cb8 100644 --- a/libs/common/src/platform/sync/sync.service.ts +++ b/libs/common/src/platform/sync/sync.service.ts @@ -7,6 +7,26 @@ import { } from "../../models/response/notification.response"; import { UserId } from "../../types/guid"; +/** + * A set of options for configuring how a {@link SyncService.fullSync} call should behave. + */ +export type SyncOptions = { + /** + * A boolean dictating whether or not caught errors should be rethrown. + * `true` if they can be rethrown, `false` if they should not be rethrown. + * @default false + */ + allowThrowOnError?: boolean; + /** + * A boolean dictating whether or not to do a token refresh before doing the sync. + * `true` if the refresh can be skipped, likely because one was done soon before the call to + * `fullSync`. `false` if the token refresh should be done before getting data. + * + * @default false + */ + skipTokenRefresh?: boolean; +}; + /** * A class encapsulating sync operations and data. */ @@ -47,9 +67,12 @@ export abstract class SyncService { * as long as the current user is authenticated. If `false` it will only sync if either a sync * has not happened before or the last sync date for the active user is before their account * revision date. Try to always use `false` if possible. - * - * @param allowThrowOnError A boolean dictating whether or not caught errors should be rethrown. - * `true` if they can be rethrown, `false` if they should not be rethrown. + * @param syncOptions Options for customizing how the sync call should behave. + */ + abstract fullSync(forceSync: boolean, syncOptions?: SyncOptions): Promise; + + /** + * @deprecated Use the overload taking {@link SyncOptions} instead. */ abstract fullSync(forceSync: boolean, allowThrowOnError?: boolean): Promise; diff --git a/libs/common/src/tools/cryptography/organization-key-encryptor.spec.ts b/libs/common/src/tools/cryptography/organization-key-encryptor.spec.ts index 3d93db81389..9f03d618cdc 100644 --- a/libs/common/src/tools/cryptography/organization-key-encryptor.spec.ts +++ b/libs/common/src/tools/cryptography/organization-key-encryptor.spec.ts @@ -22,8 +22,10 @@ describe("OrgKeyEncryptor", () => { // on this property--that the facade treats its data like a opaque objects--to trace // the data through several function calls. Should the encryptor interact with the // objects themselves, these mocks will break. - encryptService.encrypt.mockImplementation((p) => Promise.resolve(p as unknown as EncString)); - encryptService.decryptToUtf8.mockImplementation((c) => Promise.resolve(c as unknown as string)); + encryptService.encryptString.mockImplementation((p) => + Promise.resolve(p as unknown as EncString), + ); + encryptService.decryptString.mockImplementation((c) => Promise.resolve(c as unknown as string)); dataPacker.pack.mockImplementation((v) => v as string); dataPacker.unpack.mockImplementation((v: string) => v as T); }); @@ -95,7 +97,7 @@ describe("OrgKeyEncryptor", () => { // these are data flow expectations; the operations all all pass-through mocks expect(dataPacker.pack).toHaveBeenCalledWith(value); - expect(encryptService.encrypt).toHaveBeenCalledWith(value, orgKey); + expect(encryptService.encryptString).toHaveBeenCalledWith(value, orgKey); expect(result).toBe(value); }); }); @@ -117,7 +119,7 @@ describe("OrgKeyEncryptor", () => { const result = await encryptor.decrypt(secret); // these are data flow expectations; the operations all all pass-through mocks - expect(encryptService.decryptToUtf8).toHaveBeenCalledWith(secret, orgKey); + expect(encryptService.decryptString).toHaveBeenCalledWith(secret, orgKey); expect(dataPacker.unpack).toHaveBeenCalledWith(secret); expect(result).toBe(secret); }); diff --git a/libs/common/src/tools/cryptography/organization-key-encryptor.ts b/libs/common/src/tools/cryptography/organization-key-encryptor.ts index 31f3db91232..99b47c48670 100644 --- a/libs/common/src/tools/cryptography/organization-key-encryptor.ts +++ b/libs/common/src/tools/cryptography/organization-key-encryptor.ts @@ -37,7 +37,7 @@ export class OrganizationKeyEncryptor extends OrganizationEncryptor { this.assertHasValue("secret", secret); let packed = this.dataPacker.pack(secret); - const encrypted = await this.encryptService.encrypt(packed, this.key); + const encrypted = await this.encryptService.encryptString(packed, this.key); packed = null; return encrypted; @@ -46,7 +46,7 @@ export class OrganizationKeyEncryptor extends OrganizationEncryptor { async decrypt(secret: EncString): Promise> { this.assertHasValue("secret", secret); - let decrypted = await this.encryptService.decryptToUtf8(secret, this.key); + let decrypted = await this.encryptService.decryptString(secret, this.key); const unpacked = this.dataPacker.unpack(decrypted); decrypted = null; diff --git a/libs/common/src/tools/cryptography/user-key-encryptor.spec.ts b/libs/common/src/tools/cryptography/user-key-encryptor.spec.ts index e52190500b0..5bcb57ec563 100644 --- a/libs/common/src/tools/cryptography/user-key-encryptor.spec.ts +++ b/libs/common/src/tools/cryptography/user-key-encryptor.spec.ts @@ -22,8 +22,10 @@ describe("UserKeyEncryptor", () => { // on this property--that the facade treats its data like a opaque objects--to trace // the data through several function calls. Should the encryptor interact with the // objects themselves, these mocks will break. - encryptService.encrypt.mockImplementation((p) => Promise.resolve(p as unknown as EncString)); - encryptService.decryptToUtf8.mockImplementation((c) => Promise.resolve(c as unknown as string)); + encryptService.encryptString.mockImplementation((p) => + Promise.resolve(p as unknown as EncString), + ); + encryptService.decryptString.mockImplementation((c) => Promise.resolve(c as unknown as string)); dataPacker.pack.mockImplementation((v) => v as string); dataPacker.unpack.mockImplementation((v: string) => v as T); }); @@ -95,7 +97,7 @@ describe("UserKeyEncryptor", () => { // these are data flow expectations; the operations all all pass-through mocks expect(dataPacker.pack).toHaveBeenCalledWith(value); - expect(encryptService.encrypt).toHaveBeenCalledWith(value, userKey); + expect(encryptService.encryptString).toHaveBeenCalledWith(value, userKey); expect(result).toBe(value); }); }); @@ -117,7 +119,7 @@ describe("UserKeyEncryptor", () => { const result = await encryptor.decrypt(secret); // these are data flow expectations; the operations all all pass-through mocks - expect(encryptService.decryptToUtf8).toHaveBeenCalledWith(secret, userKey); + expect(encryptService.decryptString).toHaveBeenCalledWith(secret, userKey); expect(dataPacker.unpack).toHaveBeenCalledWith(secret); expect(result).toBe(secret); }); diff --git a/libs/common/src/tools/cryptography/user-key-encryptor.ts b/libs/common/src/tools/cryptography/user-key-encryptor.ts index 4b7cd1516a0..74e41f7af6d 100644 --- a/libs/common/src/tools/cryptography/user-key-encryptor.ts +++ b/libs/common/src/tools/cryptography/user-key-encryptor.ts @@ -37,7 +37,7 @@ export class UserKeyEncryptor extends UserEncryptor { this.assertHasValue("secret", secret); let packed = this.dataPacker.pack(secret); - const encrypted = await this.encryptService.encrypt(packed, this.key); + const encrypted = await this.encryptService.encryptString(packed, this.key); packed = null; return encrypted; @@ -46,7 +46,7 @@ export class UserKeyEncryptor extends UserEncryptor { async decrypt(secret: EncString): Promise> { this.assertHasValue("secret", secret); - let decrypted = await this.encryptService.decryptToUtf8(secret, this.key); + let decrypted = await this.encryptService.decryptString(secret, this.key); const unpacked = this.dataPacker.unpack(decrypted); decrypted = null; diff --git a/libs/common/src/tools/send/models/domain/send.spec.ts b/libs/common/src/tools/send/models/domain/send.spec.ts index 79f6c03adc8..7112ad7f751 100644 --- a/libs/common/src/tools/send/models/domain/send.spec.ts +++ b/libs/common/src/tools/send/models/domain/send.spec.ts @@ -112,7 +112,7 @@ describe("Send", () => { const encryptService = mock(); const keyService = mock(); - encryptService.decryptToBytes + encryptService.decryptBytes .calledWith(send.key, userKey) .mockResolvedValue(makeStaticByteArray(32)); keyService.makeSendKey.mockResolvedValue("cryptoKey" as any); diff --git a/libs/common/src/tools/send/models/domain/send.ts b/libs/common/src/tools/send/models/domain/send.ts index f12a0010fab..78d7966bb63 100644 --- a/libs/common/src/tools/send/models/domain/send.ts +++ b/libs/common/src/tools/send/models/domain/send.ts @@ -79,7 +79,8 @@ export class Send extends Domain { try { const sendKeyEncryptionKey = await keyService.getUserKey(); - model.key = await encryptService.decryptToBytes(this.key, sendKeyEncryptionKey); + // model.key is a seed used to derive a key, not a SymmetricCryptoKey + model.key = await encryptService.decryptBytes(this.key, sendKeyEncryptionKey); model.cryptoKey = await keyService.makeSendKey(model.key); // FIXME: Remove when updating file. Eslint update // eslint-disable-next-line @typescript-eslint/no-unused-vars diff --git a/libs/common/src/tools/send/services/send.service.spec.ts b/libs/common/src/tools/send/services/send.service.spec.ts index 26cc0a46708..65fd53edd75 100644 --- a/libs/common/src/tools/send/services/send.service.spec.ts +++ b/libs/common/src/tools/send/services/send.service.spec.ts @@ -477,9 +477,11 @@ describe("SendService", () => { let encryptedKey: EncString; beforeEach(() => { - encryptService.decryptToBytes.mockResolvedValue(new Uint8Array(32)); + encryptService.unwrapSymmetricKey.mockResolvedValue( + new SymmetricCryptoKey(new Uint8Array(32)), + ); encryptedKey = new EncString("Re-encrypted Send Key"); - encryptService.encrypt.mockResolvedValue(encryptedKey); + encryptService.wrapSymmetricKey.mockResolvedValue(encryptedKey); }); it("returns re-encrypted user sends", async () => { diff --git a/libs/common/src/tools/send/services/send.service.ts b/libs/common/src/tools/send/services/send.service.ts index 1b5e5f6aa31..db3834789c8 100644 --- a/libs/common/src/tools/send/services/send.service.ts +++ b/libs/common/src/tools/send/services/send.service.ts @@ -50,7 +50,7 @@ export class SendService implements InternalSendServiceAbstraction { model: SendView, file: File | ArrayBuffer, password: string, - key?: SymmetricCryptoKey, + userKey?: SymmetricCryptoKey, ): Promise<[Send, EncArrayBuffer]> { let fileData: EncArrayBuffer = null; const send = new Send(); @@ -62,15 +62,19 @@ export class SendService implements InternalSendServiceAbstraction { send.deletionDate = model.deletionDate; send.expirationDate = model.expirationDate; if (model.key == null) { + // Sends use a seed, stored in the URL fragment. This seed is used to derive the key that is used for encryption. const key = await this.keyGenerationService.createKeyWithPurpose( 128, this.sendKeyPurpose, this.sendKeySalt, ); + // key.material is the seed that can be used to re-derive the key model.key = key.material; model.cryptoKey = key.derivedKey; } if (password != null) { + // Note: Despite being called key, the passwordKey is not used for encryption. + // It is used as a static proof that the client knows the password, and has the encryption key. const passwordKey = await this.keyGenerationService.deriveKeyFromPassword( password, model.key, @@ -78,15 +82,16 @@ export class SendService implements InternalSendServiceAbstraction { ); send.password = passwordKey.keyB64; } - if (key == null) { - key = await this.keyService.getUserKey(); + if (userKey == null) { + userKey = await this.keyService.getUserKey(); } - send.key = await this.encryptService.encrypt(model.key, key); - send.name = await this.encryptService.encrypt(model.name, model.cryptoKey); - send.notes = await this.encryptService.encrypt(model.notes, model.cryptoKey); + // Key is not a SymmetricCryptoKey, but key material used to derive the cryptoKey + send.key = await this.encryptService.encryptBytes(model.key, userKey); + send.name = await this.encryptService.encryptString(model.name, model.cryptoKey); + send.notes = await this.encryptService.encryptString(model.notes, model.cryptoKey); if (send.type === SendType.Text) { send.text = new SendText(); - send.text.text = await this.encryptService.encrypt(model.text.text, model.cryptoKey); + send.text.text = await this.encryptService.encryptString(model.text.text, model.cryptoKey); send.text.hidden = model.text.hidden; } else if (send.type === SendType.File) { send.file = new SendFile(); @@ -287,8 +292,8 @@ export class SendService implements InternalSendServiceAbstraction { ) { const requests = await Promise.all( sends.map(async (send) => { - const sendKey = await this.encryptService.decryptToBytes(send.key, originalUserKey); - send.key = await this.encryptService.encrypt(sendKey, rotateUserKey); + const sendKey = await this.encryptService.unwrapSymmetricKey(send.key, originalUserKey); + send.key = await this.encryptService.wrapSymmetricKey(sendKey, rotateUserKey); return new SendWithIdRequest(send); }), ); @@ -326,8 +331,8 @@ export class SendService implements InternalSendServiceAbstraction { if (key == null) { key = await this.keyService.getUserKey(); } - const encFileName = await this.encryptService.encrypt(fileName, key); - const encFileData = await this.encryptService.encryptToBytes(new Uint8Array(data), key); + const encFileName = await this.encryptService.encryptString(fileName, key); + const encFileData = await this.encryptService.encryptFileData(new Uint8Array(data), key); return [encFileName, encFileData]; } diff --git a/libs/vault/src/notifications/abstractions/end-user-notification.service.ts b/libs/common/src/vault/notifications/abstractions/end-user-notification.service.ts similarity index 64% rename from libs/vault/src/notifications/abstractions/end-user-notification.service.ts rename to libs/common/src/vault/notifications/abstractions/end-user-notification.service.ts index fe2852994f7..bc5dd4d97a4 100644 --- a/libs/vault/src/notifications/abstractions/end-user-notification.service.ts +++ b/libs/common/src/vault/notifications/abstractions/end-user-notification.service.ts @@ -1,6 +1,6 @@ -import { Observable } from "rxjs"; +import { Observable, Subscription } from "rxjs"; -import { UserId } from "@bitwarden/common/types/guid"; +import { NotificationId, UserId } from "@bitwarden/common/types/guid"; import { NotificationView } from "../models"; @@ -25,18 +25,23 @@ export abstract class EndUserNotificationService { * @param notificationId * @param userId */ - abstract markAsRead(notificationId: any, userId: UserId): Promise; + abstract markAsRead(notificationId: NotificationId, userId: UserId): Promise; /** * Mark a notification as deleted. * @param notificationId * @param userId */ - abstract markAsDeleted(notificationId: any, userId: UserId): Promise; + abstract markAsDeleted(notificationId: NotificationId, userId: UserId): Promise; /** * Clear all notifications from state for the given user. * @param userId */ abstract clearState(userId: UserId): Promise; + + /** + * Creates a subscription to listen for end user push notifications and notification status updates. + */ + abstract listenForEndUserNotifications(): Subscription; } diff --git a/libs/common/src/vault/notifications/index.ts b/libs/common/src/vault/notifications/index.ts new file mode 100644 index 00000000000..768262be943 --- /dev/null +++ b/libs/common/src/vault/notifications/index.ts @@ -0,0 +1,2 @@ +export { EndUserNotificationService } from "./abstractions/end-user-notification.service"; +export { DefaultEndUserNotificationService } from "./services/default-end-user-notification.service"; diff --git a/libs/vault/src/notifications/models/index.ts b/libs/common/src/vault/notifications/models/index.ts similarity index 100% rename from libs/vault/src/notifications/models/index.ts rename to libs/common/src/vault/notifications/models/index.ts diff --git a/libs/vault/src/notifications/models/notification-view.data.ts b/libs/common/src/vault/notifications/models/notification-view.data.ts similarity index 85% rename from libs/vault/src/notifications/models/notification-view.data.ts rename to libs/common/src/vault/notifications/models/notification-view.data.ts index 07c147052ad..60314a44684 100644 --- a/libs/vault/src/notifications/models/notification-view.data.ts +++ b/libs/common/src/vault/notifications/models/notification-view.data.ts @@ -1,6 +1,6 @@ import { Jsonify } from "type-fest"; -import { NotificationId } from "@bitwarden/common/types/guid"; +import { NotificationId, SecurityTaskId } from "@bitwarden/common/types/guid"; import { NotificationViewResponse } from "./notification-view.response"; @@ -10,6 +10,7 @@ export class NotificationViewData { title: string; body: string; date: Date; + taskId?: SecurityTaskId; readDate: Date | null; deletedDate: Date | null; @@ -19,6 +20,7 @@ export class NotificationViewData { this.title = response.title; this.body = response.body; this.date = response.date; + this.taskId = response.taskId; this.readDate = response.readDate; this.deletedDate = response.deletedDate; } @@ -30,6 +32,7 @@ export class NotificationViewData { title: obj.title, body: obj.body, date: new Date(obj.date), + taskId: obj.taskId, readDate: obj.readDate ? new Date(obj.readDate) : null, deletedDate: obj.deletedDate ? new Date(obj.deletedDate) : null, }); diff --git a/libs/vault/src/notifications/models/notification-view.response.ts b/libs/common/src/vault/notifications/models/notification-view.response.ts similarity index 81% rename from libs/vault/src/notifications/models/notification-view.response.ts rename to libs/common/src/vault/notifications/models/notification-view.response.ts index bbebf25bd4e..b4b7d8d94cc 100644 --- a/libs/vault/src/notifications/models/notification-view.response.ts +++ b/libs/common/src/vault/notifications/models/notification-view.response.ts @@ -1,5 +1,5 @@ import { BaseResponse } from "@bitwarden/common/models/response/base.response"; -import { NotificationId } from "@bitwarden/common/types/guid"; +import { NotificationId, SecurityTaskId } from "@bitwarden/common/types/guid"; export class NotificationViewResponse extends BaseResponse { id: NotificationId; @@ -7,6 +7,7 @@ export class NotificationViewResponse extends BaseResponse { title: string; body: string; date: Date; + taskId?: SecurityTaskId; readDate: Date; deletedDate: Date; @@ -17,6 +18,7 @@ export class NotificationViewResponse extends BaseResponse { this.title = this.getResponseProperty("Title"); this.body = this.getResponseProperty("Body"); this.date = this.getResponseProperty("Date"); + this.taskId = this.getResponseProperty("TaskId"); this.readDate = this.getResponseProperty("ReadDate"); this.deletedDate = this.getResponseProperty("DeletedDate"); } diff --git a/libs/vault/src/notifications/models/notification-view.ts b/libs/common/src/vault/notifications/models/notification-view.ts similarity index 75% rename from libs/vault/src/notifications/models/notification-view.ts rename to libs/common/src/vault/notifications/models/notification-view.ts index b577a889d05..21d55ec0aed 100644 --- a/libs/vault/src/notifications/models/notification-view.ts +++ b/libs/common/src/vault/notifications/models/notification-view.ts @@ -1,4 +1,4 @@ -import { NotificationId } from "@bitwarden/common/types/guid"; +import { NotificationId, SecurityTaskId } from "@bitwarden/common/types/guid"; export class NotificationView { id: NotificationId; @@ -6,6 +6,7 @@ export class NotificationView { title: string; body: string; date: Date; + taskId?: SecurityTaskId; readDate: Date | null; deletedDate: Date | null; @@ -15,6 +16,7 @@ export class NotificationView { this.title = obj.title; this.body = obj.body; this.date = obj.date; + this.taskId = obj.taskId; this.readDate = obj.readDate; this.deletedDate = obj.deletedDate; } diff --git a/libs/common/src/vault/notifications/services/default-end-user-notification.service.spec.ts b/libs/common/src/vault/notifications/services/default-end-user-notification.service.spec.ts new file mode 100644 index 00000000000..89a78d6f7d2 --- /dev/null +++ b/libs/common/src/vault/notifications/services/default-end-user-notification.service.spec.ts @@ -0,0 +1,223 @@ +import { mock } from "jest-mock-extended"; +import { firstValueFrom, of } from "rxjs"; + +import { ApiService } from "@bitwarden/common/abstractions/api.service"; +import { AuthService } from "@bitwarden/common/auth/abstractions/auth.service"; +import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; +import { NotificationsService } from "@bitwarden/common/platform/notifications"; +import { StateProvider } from "@bitwarden/common/platform/state"; +import { NotificationId, UserId } from "@bitwarden/common/types/guid"; + +import { FakeStateProvider, mockAccountServiceWith } from "../../../../spec"; +import { NotificationViewResponse } from "../models"; +import { NOTIFICATIONS } from "../state/end-user-notification.state"; + +import { + DEFAULT_NOTIFICATION_PAGE_SIZE, + DefaultEndUserNotificationService, +} from "./default-end-user-notification.service"; + +describe("End User Notification Center Service", () => { + let fakeStateProvider: FakeStateProvider; + let mockApiService: jest.Mocked; + let mockNotificationsService: jest.Mocked; + let mockAuthService: jest.Mocked; + let mockLogService: jest.Mocked; + let service: DefaultEndUserNotificationService; + + beforeEach(() => { + fakeStateProvider = new FakeStateProvider(mockAccountServiceWith("user-id" as UserId)); + mockApiService = { + send: jest.fn(), + } as any; + mockNotificationsService = { + notifications$: of(null), + } as any; + mockAuthService = { + authStatuses$: of({}), + } as any; + mockLogService = mock(); + + service = new DefaultEndUserNotificationService( + fakeStateProvider as unknown as StateProvider, + mockApiService, + mockNotificationsService, + mockAuthService, + mockLogService, + ); + }); + + describe("notifications$", () => { + it("should return notifications from state when not null", async () => { + fakeStateProvider.singleUser.mockFor("user-id" as UserId, NOTIFICATIONS, [ + { + id: "notification-id" as NotificationId, + } as NotificationViewResponse, + ]); + + const result = await firstValueFrom(service.notifications$("user-id" as UserId)); + + expect(result.length).toBe(1); + expect(mockApiService.send).not.toHaveBeenCalled(); + expect(mockLogService.warning).not.toHaveBeenCalled(); + }); + + it("should return notifications API when state is null", async () => { + mockApiService.send.mockResolvedValue({ + data: [ + { + id: "notification-id", + }, + ] as NotificationViewResponse[], + }); + + fakeStateProvider.singleUser.mockFor("user-id" as UserId, NOTIFICATIONS, null as any); + + const result = await firstValueFrom(service.notifications$("user-id" as UserId)); + + expect(result.length).toBe(1); + expect(mockApiService.send).toHaveBeenCalledWith( + "GET", + `/notifications?pageSize=${DEFAULT_NOTIFICATION_PAGE_SIZE}`, + null, + true, + true, + ); + expect(mockLogService.warning).not.toHaveBeenCalled(); + }); + + it("should log a warning if there are more notifications available", async () => { + mockApiService.send.mockResolvedValue({ + data: [ + ...new Array(DEFAULT_NOTIFICATION_PAGE_SIZE + 1).fill({ id: "notification-id" }), + ] as NotificationViewResponse[], + continuationToken: "next-token", // Presence of continuation token indicates more data + }); + + fakeStateProvider.singleUser.mockFor("user-id" as UserId, NOTIFICATIONS, null as any); + + const result = await firstValueFrom(service.notifications$("user-id" as UserId)); + + expect(result.length).toBe(DEFAULT_NOTIFICATION_PAGE_SIZE + 1); + expect(mockApiService.send).toHaveBeenCalledWith( + "GET", + `/notifications?pageSize=${DEFAULT_NOTIFICATION_PAGE_SIZE}`, + null, + true, + true, + ); + expect(mockLogService.warning).toHaveBeenCalledWith( + `More notifications available, but not fetched. Consider increasing the page size from ${DEFAULT_NOTIFICATION_PAGE_SIZE}`, + ); + }); + + it("should share the same observable for the same user", async () => { + const first = service.notifications$("user-id" as UserId); + const second = service.notifications$("user-id" as UserId); + + expect(first).toBe(second); + }); + }); + + describe("unreadNotifications$", () => { + it("should return unread notifications from state when read value is null", async () => { + fakeStateProvider.singleUser.mockFor("user-id" as UserId, NOTIFICATIONS, [ + { + id: "notification-id" as NotificationId, + readDate: null as any, + } as NotificationViewResponse, + ]); + + const result = await firstValueFrom(service.unreadNotifications$("user-id" as UserId)); + + expect(result.length).toBe(1); + expect(mockApiService.send).not.toHaveBeenCalled(); + }); + }); + + describe("getNotifications", () => { + it("should call getNotifications returning notifications from API", async () => { + mockApiService.send.mockResolvedValue({ + data: [ + { + id: "notification-id", + }, + ] as NotificationViewResponse[], + }); + + await service.refreshNotifications("user-id" as UserId); + + expect(mockApiService.send).toHaveBeenCalledWith( + "GET", + `/notifications?pageSize=${DEFAULT_NOTIFICATION_PAGE_SIZE}`, + null, + true, + true, + ); + }); + + it("should update local state when notifications are updated", async () => { + mockApiService.send.mockResolvedValue({ + data: [ + { + id: "notification-id", + }, + ] as NotificationViewResponse[], + }); + + const mock = fakeStateProvider.singleUser.mockFor( + "user-id" as UserId, + NOTIFICATIONS, + null as any, + ); + + await service.refreshNotifications("user-id" as UserId); + + expect(mock.nextMock).toHaveBeenCalledWith([ + expect.objectContaining({ + id: "notification-id" as NotificationId, + } as NotificationViewResponse), + ]); + }); + }); + + describe("clear", () => { + it("should clear the local notification state for the user", async () => { + const mock = fakeStateProvider.singleUser.mockFor("user-id" as UserId, NOTIFICATIONS, [ + { + id: "notification-id" as NotificationId, + } as NotificationViewResponse, + ]); + + await service.clearState("user-id" as UserId); + + expect(mock.nextMock).toHaveBeenCalledWith([]); + }); + }); + + describe("markAsDeleted", () => { + it("should send an API request to mark the notification as deleted", async () => { + await service.markAsDeleted("notification-id" as NotificationId, "user-id" as UserId); + expect(mockApiService.send).toHaveBeenCalledWith( + "DELETE", + "/notifications/notification-id/delete", + null, + true, + false, + ); + }); + }); + + describe("markAsRead", () => { + it("should send an API request to mark the notification as read", async () => { + await service.markAsRead("notification-id" as NotificationId, "user-id" as UserId); + expect(mockApiService.send).toHaveBeenCalledWith( + "PATCH", + "/notifications/notification-id/read", + null, + true, + false, + ); + }); + }); +}); diff --git a/libs/common/src/vault/notifications/services/default-end-user-notification.service.ts b/libs/common/src/vault/notifications/services/default-end-user-notification.service.ts new file mode 100644 index 00000000000..87f97b48c27 --- /dev/null +++ b/libs/common/src/vault/notifications/services/default-end-user-notification.service.ts @@ -0,0 +1,213 @@ +import { concatMap, EMPTY, filter, map, Observable, Subscription, switchMap } from "rxjs"; + +import { ApiService } from "@bitwarden/common/abstractions/api.service"; +import { AuthService } from "@bitwarden/common/auth/abstractions/auth.service"; +import { AuthenticationStatus } from "@bitwarden/common/auth/enums/authentication-status"; +import { NotificationType } from "@bitwarden/common/enums"; +import { ListResponse } from "@bitwarden/common/models/response/list.response"; +import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; +import { NotificationsService } from "@bitwarden/common/platform/notifications"; +import { StateProvider } from "@bitwarden/common/platform/state"; +import { NotificationId, UserId } from "@bitwarden/common/types/guid"; +import { + filterOutNullish, + perUserCache$, +} from "@bitwarden/common/vault/utils/observable-utilities"; + +import { EndUserNotificationService } from "../abstractions/end-user-notification.service"; +import { NotificationView, NotificationViewData, NotificationViewResponse } from "../models"; +import { NOTIFICATIONS } from "../state/end-user-notification.state"; + +/** + * The default number of notifications to fetch from the API. + */ +export const DEFAULT_NOTIFICATION_PAGE_SIZE = 50; + +const getLoggedInUserIds = map, UserId[]>((authStatuses) => + Object.entries(authStatuses ?? {}) + .filter(([, status]) => status >= AuthenticationStatus.Locked) + .map(([userId]) => userId as UserId), +); + +/** + * A service for retrieving and managing notifications for end users. + */ +export class DefaultEndUserNotificationService implements EndUserNotificationService { + constructor( + private stateProvider: StateProvider, + private apiService: ApiService, + private notificationService: NotificationsService, + private authService: AuthService, + private logService: LogService, + ) {} + + notifications$ = perUserCache$((userId: UserId): Observable => { + return this.notificationState(userId).state$.pipe( + switchMap(async (notifications) => { + if (notifications == null) { + await this.fetchNotificationsFromApi(userId); + return null; + } + return notifications; + }), + filterOutNullish(), + map((notifications) => + notifications.map((notification) => new NotificationView(notification)), + ), + ); + }); + + unreadNotifications$ = perUserCache$((userId: UserId): Observable => { + return this.notifications$(userId).pipe( + map((notifications) => notifications.filter((notification) => notification.readDate == null)), + ); + }); + + async markAsRead(notificationId: NotificationId, userId: UserId): Promise { + await this.apiService.send("PATCH", `/notifications/${notificationId}/read`, null, true, false); + await this.notificationState(userId).update((current) => { + const notification = current?.find((n) => n.id === notificationId); + if (notification) { + notification.readDate = new Date(); + } + return current; + }); + } + + async markAsDeleted(notificationId: NotificationId, userId: UserId): Promise { + await this.apiService.send( + "DELETE", + `/notifications/${notificationId}/delete`, + null, + true, + false, + ); + await this.notificationState(userId).update((current) => { + const notification = current?.find((n) => n.id === notificationId); + if (notification) { + notification.deletedDate = new Date(); + } + return current; + }); + } + + async clearState(userId: UserId): Promise { + await this.replaceNotificationState(userId, []); + } + + async refreshNotifications(userId: UserId) { + await this.fetchNotificationsFromApi(userId); + } + + /** + * Helper observable to filter notifications by the notification type and user ids + * Returns EMPTY if no user ids are provided + * @param userIds + * @private + */ + private filteredEndUserNotifications$(userIds: UserId[]) { + if (userIds.length == 0) { + return EMPTY; + } + + return this.notificationService.notifications$.pipe( + filter( + ([{ type }, userId]) => + (type === NotificationType.Notification || + type === NotificationType.NotificationStatus) && + userIds.includes(userId), + ), + ); + } + + /** + * Creates a subscription to listen for end user push notifications and notification status updates. + */ + listenForEndUserNotifications(): Subscription { + return this.authService.authStatuses$ + .pipe( + getLoggedInUserIds, + switchMap((userIds) => this.filteredEndUserNotifications$(userIds)), + concatMap(([notification, userId]) => + this.upsertNotification( + userId, + new NotificationViewData(notification.payload as NotificationViewResponse), + ), + ), + ) + .subscribe(); + } + + /** + * Fetches the notifications from the API and updates the local state + * @param userId + * @private + */ + private async fetchNotificationsFromApi(userId: UserId): Promise { + const res = await this.apiService.send( + "GET", + `/notifications?pageSize=${DEFAULT_NOTIFICATION_PAGE_SIZE}`, + null, + true, + true, + ); + const response = new ListResponse(res, NotificationViewResponse); + + if (response.continuationToken != null) { + this.logService.warning( + `More notifications available, but not fetched. Consider increasing the page size from ${DEFAULT_NOTIFICATION_PAGE_SIZE}`, + ); + } + + const notificationData = response.data.map((n) => new NotificationViewData(n)); + await this.replaceNotificationState(userId, notificationData); + } + + /** + * Replaces the local state with notifications and returns the updated state + * @param userId + * @param notifications + * @private + */ + private replaceNotificationState( + userId: UserId, + notifications: NotificationViewData[], + ): Promise { + return this.notificationState(userId).update(() => notifications); + } + + /** + * Updates the local state adding the new notification or updates an existing one with the same id + * Returns the entire updated notifications state + * @param userId + * @param notification + * @private + */ + private async upsertNotification( + userId: UserId, + notification: NotificationViewData, + ): Promise { + return this.notificationState(userId).update((current) => { + current ??= []; + + const existingIndex = current.findIndex((n) => n.id === notification.id); + + if (existingIndex === -1) { + current.push(notification); + } else { + current[existingIndex] = notification; + } + + return current; + }); + } + + /** + * Returns the local state for notifications + * @param userId + * @private + */ + private notificationState(userId: UserId) { + return this.stateProvider.getUser(userId, NOTIFICATIONS); + } +} diff --git a/libs/vault/src/notifications/state/end-user-notification.state.ts b/libs/common/src/vault/notifications/state/end-user-notification.state.ts similarity index 100% rename from libs/vault/src/notifications/state/end-user-notification.state.ts rename to libs/common/src/vault/notifications/state/end-user-notification.state.ts diff --git a/libs/common/src/vault/services/cipher.service.spec.ts b/libs/common/src/vault/services/cipher.service.spec.ts index 95053ae6b4a..c9ef0ead691 100644 --- a/libs/common/src/vault/services/cipher.service.spec.ts +++ b/libs/common/src/vault/services/cipher.service.spec.ts @@ -282,6 +282,7 @@ describe("Cipher Service", () => { Promise.resolve(new SymmetricCryptoKey(makeStaticByteArray(64)) as CipherKey), ); encryptService.encrypt.mockImplementation(encryptText); + encryptService.wrapSymmetricKey.mockResolvedValue(new EncString("Re-encrypted Cipher Key")); jest.spyOn(cipherService as any, "getAutofillOnPageLoadDefault").mockResolvedValue(true); }); @@ -438,7 +439,7 @@ describe("Cipher Service", () => { encryptService.decryptToBytes.mockResolvedValue(new Uint8Array(32)); encryptedKey = new EncString("Re-encrypted Cipher Key"); - encryptService.encrypt.mockResolvedValue(encryptedKey); + encryptService.wrapSymmetricKey.mockResolvedValue(encryptedKey); keyService.makeCipherKey.mockResolvedValue( new SymmetricCryptoKey(new Uint8Array(32)) as CipherKey, diff --git a/libs/common/src/vault/services/cipher.service.ts b/libs/common/src/vault/services/cipher.service.ts index 41961c512d5..0ca683d582f 100644 --- a/libs/common/src/vault/services/cipher.service.ts +++ b/libs/common/src/vault/services/cipher.service.ts @@ -128,12 +128,8 @@ export class CipherService implements CipherServiceAbstraction { * decryption is in progress. The latest decrypted ciphers will be emitted once decryption is complete. */ cipherViews$ = perUserCache$((userId: UserId): Observable => { - return combineLatest([ - this.encryptedCiphersState(userId).state$, - this.localData$(userId), - this.keyService.cipherDecryptionKeys$(userId, true), - ]).pipe( - filter(([ciphers, keys]) => ciphers != null && keys != null), // Skip if ciphers haven't been loaded yor synced yet + return combineLatest([this.encryptedCiphersState(userId).state$, this.localData$(userId)]).pipe( + filter(([ciphers]) => ciphers != null), // Skip if ciphers haven't been loaded yor synced yet switchMap(() => this.getAllDecrypted(userId)), ); }, this.clearCipherViewsForUser$); @@ -271,7 +267,7 @@ export class CipherService implements CipherServiceAbstraction { key, ).then(async () => { if (model.key != null) { - attachment.key = await this.encryptService.encrypt(model.key.key, key); + attachment.key = await this.encryptService.wrapSymmetricKey(model.key, key); } encAttachments.push(attachment); }); @@ -1881,8 +1877,8 @@ export class CipherService implements CipherServiceAbstraction { } // Then, we have to encrypt the cipher key with the proper key. - cipher.key = await this.encryptService.encrypt( - decryptedCipherKey.key, + cipher.key = await this.encryptService.wrapSymmetricKey( + decryptedCipherKey, keyForCipherKeyEncryption, ); diff --git a/libs/common/src/vault/tasks/services/default-task.service.spec.ts b/libs/common/src/vault/tasks/services/default-task.service.spec.ts index 4d468d09766..cb22d1296ba 100644 --- a/libs/common/src/vault/tasks/services/default-task.service.spec.ts +++ b/libs/common/src/vault/tasks/services/default-task.service.spec.ts @@ -108,6 +108,34 @@ describe("Default task service", () => { }); describe("tasks$", () => { + beforeEach(() => { + mockGetFeatureFlag$.mockReturnValue(new BehaviorSubject(true)); + mockGetAllOrgs$.mockReturnValue( + new BehaviorSubject([ + { + useRiskInsights: true, + }, + ] as Organization[]), + ); + }); + + it("should return an empty array if tasks are not enabled", async () => { + mockGetAllOrgs$.mockReturnValue( + new BehaviorSubject([ + { + useRiskInsights: false, + }, + ] as Organization[]), + ); + + const { tasks$ } = service; + + const result = await firstValueFrom(tasks$("user-id" as UserId)); + + expect(result.length).toBe(0); + expect(mockApiSend).not.toHaveBeenCalled(); + }); + it("should fetch tasks from the API when the state is null", async () => { mockApiSend.mockResolvedValue({ data: [ @@ -153,6 +181,34 @@ describe("Default task service", () => { }); describe("pendingTasks$", () => { + beforeEach(() => { + mockGetFeatureFlag$.mockReturnValue(new BehaviorSubject(true)); + mockGetAllOrgs$.mockReturnValue( + new BehaviorSubject([ + { + useRiskInsights: true, + }, + ] as Organization[]), + ); + }); + + it("should return an empty array if tasks are not enabled", async () => { + mockGetAllOrgs$.mockReturnValue( + new BehaviorSubject([ + { + useRiskInsights: false, + }, + ] as Organization[]), + ); + + const { pendingTasks$ } = service; + + const result = await firstValueFrom(pendingTasks$("user-id" as UserId)); + + expect(result.length).toBe(0); + expect(mockApiSend).not.toHaveBeenCalled(); + }); + it("should filter tasks to only pending tasks", async () => { fakeStateProvider.singleUser.mockFor("user-id" as UserId, SECURITY_TASKS, [ { diff --git a/libs/common/src/vault/tasks/services/default-task.service.ts b/libs/common/src/vault/tasks/services/default-task.service.ts index 7386102263c..a50f736f7fd 100644 --- a/libs/common/src/vault/tasks/services/default-task.service.ts +++ b/libs/common/src/vault/tasks/services/default-task.service.ts @@ -1,4 +1,14 @@ -import { combineLatest, filter, map, merge, Observable, of, Subscription, switchMap } from "rxjs"; +import { + combineLatest, + filter, + map, + merge, + Observable, + of, + Subscription, + switchMap, + distinctUntilChanged, +} from "rxjs"; import { ApiService } from "@bitwarden/common/abstractions/api.service"; import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction"; @@ -45,20 +55,30 @@ export class DefaultTaskService implements TaskService { .organizations$(userId) .pipe(map((orgs) => orgs.some((o) => o.useRiskInsights))), this.configService.getFeatureFlag$(FeatureFlag.SecurityTasks), - ]).pipe(map(([atLeastOneOrgEnabled, flagEnabled]) => atLeastOneOrgEnabled && flagEnabled)); + ]).pipe( + map(([atLeastOneOrgEnabled, flagEnabled]) => atLeastOneOrgEnabled && flagEnabled), + distinctUntilChanged(), + ); }); tasks$ = perUserCache$((userId) => { - return this.taskState(userId).state$.pipe( - switchMap(async (tasks) => { - if (tasks == null) { - await this.fetchTasksFromApi(userId); - return null; + return this.tasksEnabled$(userId).pipe( + switchMap((enabled) => { + if (!enabled) { + return of([]); } - return tasks; + return this.taskState(userId).state$.pipe( + switchMap(async (tasks) => { + if (tasks == null) { + await this.fetchTasksFromApi(userId); + return null; + } + return tasks; + }), + filterOutNullish(), + map((tasks) => tasks.map((t) => new SecurityTask(t))), + ); }), - filterOutNullish(), - map((tasks) => tasks.map((t) => new SecurityTask(t))), ); }); diff --git a/libs/components/src/button/button.component.ts b/libs/components/src/button/button.component.ts index 0b4ce3073c3..19618938c42 100644 --- a/libs/components/src/button/button.component.ts +++ b/libs/components/src/button/button.component.ts @@ -1,12 +1,10 @@ -// FIXME: Update this file to be type safe and remove this and next line -// @ts-strict-ignore import { coerceBooleanProperty } from "@angular/cdk/coercion"; import { NgClass } from "@angular/common"; -import { Input, HostBinding, Component, model, computed } from "@angular/core"; +import { Input, HostBinding, Component, model, computed, input } from "@angular/core"; import { toObservable, toSignal } from "@angular/core/rxjs-interop"; import { debounce, interval } from "rxjs"; -import { ButtonLikeAbstraction, ButtonType } from "../shared/button-like.abstraction"; +import { ButtonLikeAbstraction, ButtonType, ButtonSize } from "../shared/button-like.abstraction"; const focusRing = [ "focus-visible:tw-ring-2", @@ -15,6 +13,11 @@ const focusRing = [ "focus-visible:tw-z-10", ]; +const buttonSizeStyles: Record = { + small: ["tw-py-1", "tw-px-3", "tw-text-sm"], + default: ["tw-py-1.5", "tw-px-3"], +}; + const buttonStyles: Record = { primary: [ "tw-border-primary-600", @@ -59,8 +62,6 @@ export class ButtonComponent implements ButtonLikeAbstraction { @HostBinding("class") get classList() { return [ "tw-font-semibold", - "tw-py-1.5", - "tw-px-3", "tw-rounded-full", "tw-transition", "tw-border-2", @@ -85,7 +86,8 @@ export class ButtonComponent implements ButtonLikeAbstraction { "disabled:hover:tw-no-underline", ] : [], - ); + ) + .concat(buttonSizeStyles[this.size() || "default"]); } protected disabledAttr = computed(() => { @@ -105,7 +107,9 @@ export class ButtonComponent implements ButtonLikeAbstraction { return this.showLoadingStyle() || (this.disabledAttr() && this.loading() === false); }); - @Input() buttonType: ButtonType; + @Input() buttonType: ButtonType = "secondary"; + + size = input("default"); private _block = false; diff --git a/libs/components/src/button/button.stories.ts b/libs/components/src/button/button.stories.ts index 448e290cce8..759bd1a352c 100644 --- a/libs/components/src/button/button.stories.ts +++ b/libs/components/src/button/button.stories.ts @@ -9,6 +9,13 @@ export default { buttonType: "primary", disabled: false, loading: false, + size: "default", + }, + argTypes: { + size: { + options: ["small", "default"], + control: { type: "radio" }, + }, }, parameters: { design: { @@ -24,19 +31,19 @@ export const Primary: Story = { render: (args) => ({ props: args, template: /*html*/ ` - - Button - Button:hover - Button:focus-visible - Button:hover:focus-visible - Button:active + + Button + Button:hover + Button:focus-visible + Button:hover:focus-visible + Button:active - - Anchor - Anchor:hover - Anchor:focus-visible - Anchor:hover:focus-visible - Anchor:active + + Anchor + Anchor:hover + Anchor:focus-visible + Anchor:hover:focus-visible + Anchor:active `, }), @@ -59,6 +66,22 @@ export const Danger: Story = { }, }; +export const Small: Story = { + render: (args) => ({ + props: args, + template: /*html*/ ` + + Primary small + Secondary small + Danger small + + `, + }), + args: { + size: "small", + }, +}; + export const Loading: Story = { render: (args) => ({ props: args, diff --git a/libs/components/src/icon-button/index.ts b/libs/components/src/icon-button/index.ts index 9da4a3162bf..cc52f263252 100644 --- a/libs/components/src/icon-button/index.ts +++ b/libs/components/src/icon-button/index.ts @@ -1 +1,2 @@ export * from "./icon-button.module"; +export { BitIconButtonComponent } from "./icon-button.component"; diff --git a/libs/components/src/input/autofocus.directive.ts b/libs/components/src/input/autofocus.directive.ts index 27abccdd45f..46eb1b15b16 100644 --- a/libs/components/src/input/autofocus.directive.ts +++ b/libs/components/src/input/autofocus.directive.ts @@ -1,6 +1,6 @@ // FIXME: Update this file to be type safe and remove this and next line // @ts-strict-ignore -import { Directive, ElementRef, Input, NgZone, OnInit, Optional } from "@angular/core"; +import { AfterContentChecked, Directive, ElementRef, Input, NgZone, Optional } from "@angular/core"; import { take } from "rxjs/operators"; import { Utils } from "@bitwarden/common/platform/misc/utils"; @@ -12,40 +12,72 @@ import { FocusableElement } from "../shared/focusable-element"; * * @remarks * + * Will focus the element once, when it becomes visible. + * * If the component provides the `FocusableElement` interface, the `focus` * method will be called. Otherwise, the native element will be focused. */ @Directive({ selector: "[appAutofocus], [bitAutofocus]", }) -export class AutofocusDirective implements OnInit { +export class AutofocusDirective implements AfterContentChecked { @Input() set appAutofocus(condition: boolean | string) { this.autofocus = condition === "" || condition === true; } private autofocus: boolean; + // Track if we have already focused the element. + private focused = false; + constructor( private el: ElementRef, private ngZone: NgZone, @Optional() private focusableElement: FocusableElement, ) {} - ngOnInit() { - if (!Utils.isMobileBrowser && this.autofocus) { - if (this.ngZone.isStable) { - this.focus(); - } else { - this.ngZone.onStable.pipe(take(1)).subscribe(this.focus.bind(this)); - } + /** + * Using AfterContentChecked is a hack to ensure we only focus once. This is because + * the element may not be in the DOM, or not be focusable when the directive is + * created, and we want to wait until it is. + * + * Note: This might break in the future since it relies on Angular change detection + * to trigger after the element becomes visible. + */ + ngAfterContentChecked() { + // We only want to focus the element on initial render and it's not a mobile browser + if (this.focused || !this.autofocus || Utils.isMobileBrowser) { + return; + } + + const el = this.getElement(); + if (el == null) { + return; + } + + if (this.ngZone.isStable) { + this.focus(); + } else { + this.ngZone.onStable.pipe(take(1)).subscribe(this.focus.bind(this)); } } + /** + * Attempt to focus the element. If successful we set focused to true to prevent further focus + * attempts. + */ private focus() { + const el = this.getElement(); + + el.focus(); + this.focused = el === document.activeElement; + } + + private getElement() { if (this.focusableElement) { - this.focusableElement.getFocusTarget().focus(); - } else { - this.el.nativeElement.focus(); + return this.focusableElement.getFocusTarget(); } + + return this.el.nativeElement; } } diff --git a/libs/components/src/search/search.component.ts b/libs/components/src/search/search.component.ts index 7f1bd781e9d..7edf3b1d60a 100644 --- a/libs/components/src/search/search.component.ts +++ b/libs/components/src/search/search.component.ts @@ -49,7 +49,7 @@ export class SearchComponent implements ControlValueAccessor, FocusableElement { @Input() autocomplete: string; getFocusTarget() { - return this.input.nativeElement; + return this.input?.nativeElement; } onChange(searchText: string) { diff --git a/libs/components/src/shared/button-like.abstraction.ts b/libs/components/src/shared/button-like.abstraction.ts index 5ee9d272594..c7cb620bff0 100644 --- a/libs/components/src/shared/button-like.abstraction.ts +++ b/libs/components/src/shared/button-like.abstraction.ts @@ -4,6 +4,8 @@ import { ModelSignal } from "@angular/core"; // @ts-strict-ignore export type ButtonType = "primary" | "secondary" | "danger" | "unstyled"; +export type ButtonSize = "default" | "small"; + export abstract class ButtonLikeAbstraction { loading: ModelSignal; disabled: ModelSignal; diff --git a/libs/components/src/shared/focusable-element.ts b/libs/components/src/shared/focusable-element.ts index 7b063f4ddc9..99340d5a7bf 100644 --- a/libs/components/src/shared/focusable-element.ts +++ b/libs/components/src/shared/focusable-element.ts @@ -6,5 +6,5 @@ * Used by the `AutofocusDirective` and `A11yGridDirective`. */ export abstract class FocusableElement { - getFocusTarget: () => HTMLElement; + getFocusTarget: () => HTMLElement | undefined; } diff --git a/libs/components/src/variables.scss b/libs/components/src/variables.scss index d278746bb2e..bc9cded4981 100644 --- a/libs/components/src/variables.scss +++ b/libs/components/src/variables.scss @@ -20,8 +20,9 @@ $theme-colors: ( $body-bg: $white; $body-color: #333333; -$font-family-sans-serif: "DM Sans", "Helvetica Neue", Helvetica, Arial, sans-serif, - "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol"; +$font-family-sans-serif: + "DM Sans", "Helvetica Neue", Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", + "Segoe UI Symbol"; $h1-font-size: 1.7rem; $h2-font-size: 1.3rem; diff --git a/libs/importer/src/importers/bitwarden/bitwarden-json-importer.ts b/libs/importer/src/importers/bitwarden/bitwarden-json-importer.ts index f01e6571439..9284718a063 100644 --- a/libs/importer/src/importers/bitwarden/bitwarden-json-importer.ts +++ b/libs/importer/src/importers/bitwarden/bitwarden-json-importer.ts @@ -72,7 +72,7 @@ export class BitwardenJsonImporter extends BaseImporter implements Importer { keyForDecryption = await this.keyService.getUserKeyWithLegacySupport(); } const encKeyValidation = new EncString(results.encKeyValidation_DO_NOT_EDIT); - const encKeyValidationDecrypt = await this.encryptService.decryptToUtf8( + const encKeyValidationDecrypt = await this.encryptService.decryptString( encKeyValidation, keyForDecryption, ); diff --git a/libs/importer/src/importers/bitwarden/bitwarden-password-protected-importer.spec.ts b/libs/importer/src/importers/bitwarden/bitwarden-password-protected-importer.spec.ts index 66deabf0634..8d0f5dfcc1c 100644 --- a/libs/importer/src/importers/bitwarden/bitwarden-password-protected-importer.spec.ts +++ b/libs/importer/src/importers/bitwarden/bitwarden-password-protected-importer.spec.ts @@ -92,7 +92,7 @@ describe("BitwardenPasswordProtectedImporter", () => { }); it("succeeds with default jdoc", async () => { - encryptService.decryptToUtf8.mockReturnValue(Promise.resolve(emptyUnencryptedExport)); + encryptService.decryptString.mockReturnValue(Promise.resolve(emptyUnencryptedExport)); expect((await importer.parse(JSON.stringify(jDoc))).success).toEqual(true); }); diff --git a/libs/importer/src/importers/bitwarden/bitwarden-password-protected-importer.ts b/libs/importer/src/importers/bitwarden/bitwarden-password-protected-importer.ts index 02a417c2169..878f9cf5819 100644 --- a/libs/importer/src/importers/bitwarden/bitwarden-password-protected-importer.ts +++ b/libs/importer/src/importers/bitwarden/bitwarden-password-protected-importer.ts @@ -69,7 +69,7 @@ export class BitwardenPasswordProtectedImporter extends BitwardenJsonImporter im } const encData = new EncString(parsedData.data); - const clearTextData = await this.encryptService.decryptToUtf8(encData, this.key); + const clearTextData = await this.encryptService.decryptString(encData, this.key); return await super.parse(clearTextData); } @@ -90,7 +90,7 @@ export class BitwardenPasswordProtectedImporter extends BitwardenJsonImporter im const encKeyValidation = new EncString(jdoc.encKeyValidation_DO_NOT_EDIT); - const encKeyValidationDecrypt = await this.encryptService.decryptToUtf8( + const encKeyValidationDecrypt = await this.encryptService.decryptString( encKeyValidation, this.key, ); diff --git a/libs/key-management-ui/src/lock/components/lock.component.html b/libs/key-management-ui/src/lock/components/lock.component.html index 437e29447e2..efc7fb26a2f 100644 --- a/libs/key-management-ui/src/lock/components/lock.component.html +++ b/libs/key-management-ui/src/lock/components/lock.component.html @@ -1,10 +1,10 @@ - - + + - + (); + protected loading = true; activeAccount: Account | null = null; @@ -122,6 +125,9 @@ export class LockComponent implements OnInit, OnDestroy { formGroup: FormGroup | null = null; + // Browser extension properties: + private shouldClosePopout = false; + // Desktop properties: private deferFocus: boolean | null = null; private biometricAsked = false; @@ -228,22 +234,22 @@ export class LockComponent implements OnInit, OnDestroy { private listenForActiveAccountChanges() { this.accountService.activeAccount$ .pipe( - switchMap((account) => { - return this.handleActiveAccountChange(account); + tap((account) => { + this.loading = true; + this.activeAccount = account; + this.resetDataOnActiveAccountChange(); + }), + filter((account): account is Account => account != null), + switchMap(async (account) => { + await this.handleActiveAccountChange(account); + this.loading = false; }), takeUntil(this.destroy$), ) .subscribe(); } - private async handleActiveAccountChange(activeAccount: Account | null) { - this.activeAccount = activeAccount; - - this.resetDataOnActiveAccountChange(); - - if (activeAccount == null) { - return; - } + private async handleActiveAccountChange(activeAccount: Account) { // this account may be unlocked, prevent any prompts so we can redirect to vault if (await this.keyService.hasUserKeyInMemory(activeAccount.id)) { return; @@ -300,16 +306,12 @@ export class LockComponent implements OnInit, OnDestroy { // desktop and extension. if (this.clientType === "desktop") { if (autoPromptBiometrics) { + this.loading = false; await this.desktopAutoPromptBiometrics(); } } if (this.clientType === "browser") { - // Firefox closes the popup when unfocused, so this would block all unlock methods - if (this.platformUtilsService.getDevice() === DeviceType.FirefoxExtension) { - return; - } - if ( this.unlockOptions?.biometrics.enabled && autoPromptBiometrics && @@ -323,6 +325,12 @@ export class LockComponent implements OnInit, OnDestroy { isNaN(lastProcessReload.getTime()) || Date.now() - lastProcessReload.getTime() > AUTOPROMPT_BIOMETRICS_PROCESS_RELOAD_DELAY ) { + // Firefox extension closes the popup when unfocused during biometric unlock, pop out the window to prevent infinite loop. + if (this.platformUtilsService.getDevice() === DeviceType.FirefoxExtension) { + await this.lockComponentService.popOutBrowserExtension(); + this.shouldClosePopout = true; + } + this.loading = false; await this.unlockViaBiometrics(); } } @@ -637,6 +645,13 @@ export class LockComponent implements OnInit, OnDestroy { const successRoute = clientTypeToSuccessRouteRecord[this.clientType]; await this.router.navigate([successRoute]); } + + if ( + this.shouldClosePopout && + this.platformUtilsService.getDevice() === DeviceType.FirefoxExtension + ) { + this.lockComponentService.closeBrowserExtensionPopout(); + } } /** diff --git a/libs/key-management-ui/src/lock/services/lock-component.service.ts b/libs/key-management-ui/src/lock/services/lock-component.service.ts index cf5027258dd..0fc25ca7dfb 100644 --- a/libs/key-management-ui/src/lock/services/lock-component.service.ts +++ b/libs/key-management-ui/src/lock/services/lock-component.service.ts @@ -33,6 +33,18 @@ export abstract class LockComponentService { // Extension abstract getBiometricsError(error: any): string | null; abstract getPreviousUrl(): string | null; + /** + * Opens the current page in a popout window if not already in a popout or the sidebar. + * If already in a popout or sidebar, does nothing. + * @throws Error if execution context is not a browser extension. + */ + abstract popOutBrowserExtension(): Promise; + /** + * Closes the current popout window if in a popout. + * If not in a popout, does nothing. + * @throws Error if execution context is not a browser extension. + */ + abstract closeBrowserExtensionPopout(): void; // Desktop only abstract isWindowVisible(): Promise; diff --git a/libs/key-management/src/abstractions/kdf-config.service.ts b/libs/key-management/src/abstractions/kdf-config.service.ts index 9cc39561aa8..c6c4e5d4fb0 100644 --- a/libs/key-management/src/abstractions/kdf-config.service.ts +++ b/libs/key-management/src/abstractions/kdf-config.service.ts @@ -6,6 +6,6 @@ import { KdfConfig } from "../models/kdf-config"; export abstract class KdfConfigService { abstract setKdfConfig(userId: UserId, KdfConfig: KdfConfig): Promise; - abstract getKdfConfig(): Promise; + abstract getKdfConfig(userId: UserId): Promise; abstract getKdfConfig$(userId: UserId): Observable; } diff --git a/libs/key-management/src/kdf-config.service.spec.ts b/libs/key-management/src/kdf-config.service.spec.ts index 986d7abac40..97684266f5d 100644 --- a/libs/key-management/src/kdf-config.service.spec.ts +++ b/libs/key-management/src/kdf-config.service.spec.ts @@ -26,90 +26,94 @@ describe("KdfConfigService", () => { sutKdfConfigService = new DefaultKdfConfigService(fakeStateProvider); }); - it("setKdfConfig(): should set the PBKDF2KdfConfig config", async () => { - const kdfConfig: KdfConfig = new PBKDF2KdfConfig(500_000); - await sutKdfConfigService.setKdfConfig(mockUserId, kdfConfig); - expect(fakeStateProvider.mock.setUserState).toHaveBeenCalledWith( - KDF_CONFIG, - kdfConfig, - mockUserId, - ); + describe("setKdfConfig", () => { + it("sets the PBKDF2KdfConfig config", async () => { + const kdfConfig: KdfConfig = new PBKDF2KdfConfig(500_000); + await sutKdfConfigService.setKdfConfig(mockUserId, kdfConfig); + expect(fakeStateProvider.mock.setUserState).toHaveBeenCalledWith( + KDF_CONFIG, + kdfConfig, + mockUserId, + ); + }); + + it("sets the Argon2KdfConfig config", async () => { + const kdfConfig: KdfConfig = new Argon2KdfConfig(2, 63, 3); + await sutKdfConfigService.setKdfConfig(mockUserId, kdfConfig); + expect(fakeStateProvider.mock.setUserState).toHaveBeenCalledWith( + KDF_CONFIG, + kdfConfig, + mockUserId, + ); + }); + + it("throws error KDF cannot be null", async () => { + try { + await sutKdfConfigService.setKdfConfig(mockUserId, null as unknown as KdfConfig); + } catch (e) { + expect(e).toEqual(new Error("kdfConfig cannot be null")); + } + }); + + it("throws error userId cannot be null", async () => { + const kdfConfig: KdfConfig = new Argon2KdfConfig(3, 64, 4); + try { + await sutKdfConfigService.setKdfConfig(null as unknown as UserId, kdfConfig); + } catch (e) { + expect(e).toEqual(new Error("userId cannot be null")); + } + }); }); - it("setKdfConfig(): should set the Argon2KdfConfig config", async () => { - const kdfConfig: KdfConfig = new Argon2KdfConfig(2, 63, 3); - await sutKdfConfigService.setKdfConfig(mockUserId, kdfConfig); - expect(fakeStateProvider.mock.setUserState).toHaveBeenCalledWith( - KDF_CONFIG, - kdfConfig, - mockUserId, - ); + describe("getKdfConfig", () => { + it("throws error if userId is null", async () => { + await expect(sutKdfConfigService.getKdfConfig(null as unknown as UserId)).rejects.toThrow( + "userId cannot be null", + ); + }); + + it("throws if target user doesn't have a KkfConfig", async () => { + const errorMessage = "KdfConfig for user " + mockUserId + " is null"; + await expect(sutKdfConfigService.getKdfConfig(mockUserId)).rejects.toThrow(errorMessage); + }); + + it("returns KdfConfig of target user", async () => { + const kdfConfig: KdfConfig = new PBKDF2KdfConfig(500_000); + await fakeStateProvider.setUserState(KDF_CONFIG, kdfConfig, mockUserId); + await expect(sutKdfConfigService.getKdfConfig(mockUserId)).resolves.toEqual(kdfConfig); + }); }); - it("setKdfConfig(): should throw error KDF cannot be null", async () => { - try { - await sutKdfConfigService.setKdfConfig(mockUserId, null as unknown as KdfConfig); - } catch (e) { - expect(e).toEqual(new Error("kdfConfig cannot be null")); - } - }); + describe("getKdfConfig$", () => { + it("gets KdfConfig of provided user", async () => { + await expect( + firstValueFrom(sutKdfConfigService.getKdfConfig$(mockUserId)), + ).resolves.toBeNull(); + const kdfConfig: KdfConfig = new PBKDF2KdfConfig(500_000); + await fakeStateProvider.setUserState(KDF_CONFIG, kdfConfig, mockUserId); + await expect(firstValueFrom(sutKdfConfigService.getKdfConfig$(mockUserId))).resolves.toEqual( + kdfConfig, + ); + }); - it("setKdfConfig(): should throw error userId cannot be null", async () => { - const kdfConfig: KdfConfig = new Argon2KdfConfig(3, 64, 4); - try { - await sutKdfConfigService.setKdfConfig(null as unknown as UserId, kdfConfig); - } catch (e) { - expect(e).toEqual(new Error("userId cannot be null")); - } - }); + it("gets KdfConfig of provided user after changed", async () => { + await expect( + firstValueFrom(sutKdfConfigService.getKdfConfig$(mockUserId)), + ).resolves.toBeNull(); + await fakeStateProvider.setUserState(KDF_CONFIG, new PBKDF2KdfConfig(500_000), mockUserId); + const kdfConfigChanged: KdfConfig = new PBKDF2KdfConfig(500_001); + await fakeStateProvider.setUserState(KDF_CONFIG, kdfConfigChanged, mockUserId); + await expect(firstValueFrom(sutKdfConfigService.getKdfConfig$(mockUserId))).resolves.toEqual( + kdfConfigChanged, + ); + }); - it("getKdfConfig(): should get KdfConfig of active user", async () => { - const kdfConfig: KdfConfig = new PBKDF2KdfConfig(500_000); - await fakeStateProvider.setUserState(KDF_CONFIG, kdfConfig, mockUserId); - await expect(sutKdfConfigService.getKdfConfig()).resolves.toEqual(kdfConfig); - }); - - it("getKdfConfig(): should throw error KdfConfig can only be retrieved when there is active user", async () => { - fakeAccountService.activeAccountSubject.next(null); - try { - await sutKdfConfigService.getKdfConfig(); - } catch (e) { - expect(e).toEqual(new Error("KdfConfig can only be retrieved when there is active user")); - } - }); - - it("getKdfConfig(): should throw error KdfConfig for active user account state is null", async () => { - try { - await sutKdfConfigService.getKdfConfig(); - } catch (e) { - expect(e).toEqual(new Error("KdfConfig for active user account state is null")); - } - }); - - it("getKdfConfig$(UserId): should get KdfConfig of provided user", async () => { - await expect(firstValueFrom(sutKdfConfigService.getKdfConfig$(mockUserId))).resolves.toBeNull(); - const kdfConfig: KdfConfig = new PBKDF2KdfConfig(500_000); - await fakeStateProvider.setUserState(KDF_CONFIG, kdfConfig, mockUserId); - await expect(firstValueFrom(sutKdfConfigService.getKdfConfig$(mockUserId))).resolves.toEqual( - kdfConfig, - ); - }); - - it("getKdfConfig$(UserId): should get KdfConfig of provided user after changed", async () => { - await expect(firstValueFrom(sutKdfConfigService.getKdfConfig$(mockUserId))).resolves.toBeNull(); - await fakeStateProvider.setUserState(KDF_CONFIG, new PBKDF2KdfConfig(500_000), mockUserId); - const kdfConfigChanged: KdfConfig = new PBKDF2KdfConfig(500_001); - await fakeStateProvider.setUserState(KDF_CONFIG, kdfConfigChanged, mockUserId); - await expect(firstValueFrom(sutKdfConfigService.getKdfConfig$(mockUserId))).resolves.toEqual( - kdfConfigChanged, - ); - }); - - it("getKdfConfig$(UserId): should throw error userId cannot be null", async () => { - try { - sutKdfConfigService.getKdfConfig$(null as unknown as UserId); - } catch (e) { - expect(e).toEqual(new Error("userId cannot be null")); - } + it("throws error userId cannot be null", async () => { + try { + sutKdfConfigService.getKdfConfig$(null as unknown as UserId); + } catch (e) { + expect(e).toEqual(new Error("userId cannot be null")); + } + }); }); }); diff --git a/libs/key-management/src/kdf-config.service.ts b/libs/key-management/src/kdf-config.service.ts index efc5310e5a8..24635e87580 100644 --- a/libs/key-management/src/kdf-config.service.ts +++ b/libs/key-management/src/kdf-config.service.ts @@ -37,14 +37,14 @@ export class DefaultKdfConfigService implements KdfConfigService { await this.stateProvider.setUserState(KDF_CONFIG, kdfConfig, userId); } - async getKdfConfig(): Promise { - const userId = await firstValueFrom(this.stateProvider.activeUserId$); + async getKdfConfig(userId: UserId): Promise { if (userId == null) { - throw new Error("KdfConfig can only be retrieved when there is active user"); + throw new Error("userId cannot be null"); } + const state = await firstValueFrom(this.stateProvider.getUser(userId, KDF_CONFIG).state$); if (state == null) { - throw new Error("KdfConfig for active user account state is null"); + throw new Error("KdfConfig for user " + userId + " is null"); } return state; } diff --git a/libs/key-management/src/key.service.spec.ts b/libs/key-management/src/key.service.spec.ts index 90d049b7293..57291462924 100644 --- a/libs/key-management/src/key.service.spec.ts +++ b/libs/key-management/src/key.service.spec.ts @@ -497,7 +497,7 @@ describe("keyService", () => { const output = new Uint8Array(64); output.set(encryptedPrivateKey.dataBytes); output.set( - key.key.subarray(0, 64 - encryptedPrivateKey.dataBytes.length), + key.toEncoded().subarray(0, 64 - encryptedPrivateKey.dataBytes.length), encryptedPrivateKey.dataBytes.length, ); return output; @@ -827,7 +827,7 @@ describe("keyService", () => { masterPasswordService.masterKeyHashSubject.next(storedMasterKeyHash); cryptoFunctionService.pbkdf2 - .calledWith(masterKey.key, masterPassword as string, "sha256", 2) + .calledWith(masterKey.inner().encryptionKey, masterPassword as string, "sha256", 2) .mockResolvedValue(Utils.fromB64ToArray(mockReturnedHash)); const actualDidMatch = await keyService.compareKeyHash( diff --git a/libs/key-management/src/key.service.ts b/libs/key-management/src/key.service.ts index baf1b86e160..a3b6a1fa2d1 100644 --- a/libs/key-management/src/key.service.ts +++ b/libs/key-management/src/key.service.ts @@ -26,7 +26,7 @@ import { KeyGenerationService } from "@bitwarden/common/platform/abstractions/ke import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; import { StateService } from "@bitwarden/common/platform/abstractions/state.service"; -import { KeySuffixOptions, HashPurpose } from "@bitwarden/common/platform/enums"; +import { KeySuffixOptions, HashPurpose, EncryptionType } from "@bitwarden/common/platform/enums"; import { convertValues } from "@bitwarden/common/platform/misc/convert-values"; import { Utils } from "@bitwarden/common/platform/misc/utils"; import { EFFLongWordList } from "@bitwarden/common/platform/misc/wordlist"; @@ -232,7 +232,7 @@ export class DefaultKeyService implements KeyServiceAbstraction { } const newUserKey = await this.keyGenerationService.createKey(512); - return this.buildProtectedSymmetricKey(masterKey, newUserKey.key); + return this.buildProtectedSymmetricKey(masterKey, newUserKey); } /** @@ -323,7 +323,7 @@ export class DefaultKeyService implements KeyServiceAbstraction { userKey?: UserKey, ): Promise<[UserKey, EncString]> { userKey ||= await this.getUserKey(); - return await this.buildProtectedSymmetricKey(masterKey, userKey.key); + return await this.buildProtectedSymmetricKey(masterKey, userKey); } // TODO: move to MasterPasswordService @@ -346,7 +346,12 @@ export class DefaultKeyService implements KeyServiceAbstraction { } const iterations = hashPurpose === HashPurpose.LocalAuthorization ? 2 : 1; - const hash = await this.cryptoFunctionService.pbkdf2(key.key, password, "sha256", iterations); + const hash = await this.cryptoFunctionService.pbkdf2( + key.inner().encryptionKey, + password, + "sha256", + iterations, + ); return Utils.fromBufferToB64(hash); } @@ -433,7 +438,7 @@ export class DefaultKeyService implements KeyServiceAbstraction { } const newSymKey = await this.keyGenerationService.createKey(512); - return this.buildProtectedSymmetricKey(key, newSymKey.key); + return this.buildProtectedSymmetricKey(key, newSymKey); } private async clearOrgKeys(userId: UserId): Promise { @@ -547,7 +552,7 @@ export class DefaultKeyService implements KeyServiceAbstraction { const keyPair = await this.cryptoFunctionService.rsaGenerateKeyPair(2048); const publicB64 = Utils.fromBufferToB64(keyPair[0]); - const privateEnc = await this.encryptService.encrypt(keyPair[1], key); + const privateEnc = await this.encryptService.wrapDecapsulationKey(keyPair[1], key); return [publicB64, privateEnc]; } @@ -820,18 +825,21 @@ export class DefaultKeyService implements KeyServiceAbstraction { private async buildProtectedSymmetricKey( encryptionKey: SymmetricCryptoKey, - newSymKey: Uint8Array, + newSymKey: SymmetricCryptoKey, ): Promise<[T, EncString]> { let protectedSymKey: EncString; - if (encryptionKey.key.byteLength === 32) { + if (encryptionKey.inner().type === EncryptionType.AesCbc256_B64) { const stretchedEncryptionKey = await this.keyGenerationService.stretchKey(encryptionKey); - protectedSymKey = await this.encryptService.encrypt(newSymKey, stretchedEncryptionKey); - } else if (encryptionKey.key.byteLength === 64) { - protectedSymKey = await this.encryptService.encrypt(newSymKey, encryptionKey); + protectedSymKey = await this.encryptService.wrapSymmetricKey( + newSymKey, + stretchedEncryptionKey, + ); + } else if (encryptionKey.inner().type === EncryptionType.AesCbc256_HmacSha256_B64) { + protectedSymKey = await this.encryptService.wrapSymmetricKey(newSymKey, encryptionKey); } else { throw new Error("Invalid key size."); } - return [new SymmetricCryptoKey(newSymKey) as T, protectedSymKey]; + return [newSymKey as T, protectedSymKey]; } userKey$(userId: UserId): Observable { diff --git a/libs/node/src/services/node-crypto-function.service.ts b/libs/node/src/services/node-crypto-function.service.ts index 78d72d44104..33ea3adf357 100644 --- a/libs/node/src/services/node-crypto-function.service.ts +++ b/libs/node/src/services/node-crypto-function.service.ts @@ -3,6 +3,7 @@ import * as crypto from "crypto"; import * as forge from "node-forge"; import { CryptoFunctionService } from "@bitwarden/common/key-management/crypto/abstractions/crypto-function.service"; +import { EncryptionType } from "@bitwarden/common/platform/enums"; import { Utils } from "@bitwarden/common/platform/misc/utils"; import { CbcDecryptParameters, @@ -172,24 +173,33 @@ export class NodeCryptoFunctionService implements CryptoFunctionService { mac: string | null, key: SymmetricCryptoKey, ): CbcDecryptParameters { - const p = {} as CbcDecryptParameters; - p.encKey = key.encKey; - p.data = Utils.fromB64ToArray(data); - p.iv = Utils.fromB64ToArray(iv); + const dataBytes = Utils.fromB64ToArray(data); + const ivBytes = Utils.fromB64ToArray(iv); + const macBytes = mac != null ? Utils.fromB64ToArray(mac) : null; - const macData = new Uint8Array(p.iv.byteLength + p.data.byteLength); - macData.set(new Uint8Array(p.iv), 0); - macData.set(new Uint8Array(p.data), p.iv.byteLength); - p.macData = macData; + const innerKey = key.inner(); - if (key.macKey != null) { - p.macKey = key.macKey; + if (innerKey.type === EncryptionType.AesCbc256_B64) { + return { + iv: ivBytes, + data: dataBytes, + encKey: innerKey.encryptionKey, + } as CbcDecryptParameters; + } else if (innerKey.type === EncryptionType.AesCbc256_HmacSha256_B64) { + const macData = new Uint8Array(ivBytes.byteLength + dataBytes.byteLength); + macData.set(new Uint8Array(ivBytes), 0); + macData.set(new Uint8Array(dataBytes), ivBytes.byteLength); + return { + iv: ivBytes, + data: dataBytes, + mac: macBytes, + macData: macData, + encKey: innerKey.encryptionKey, + macKey: innerKey.authenticationKey, + } as CbcDecryptParameters; + } else { + throw new Error("Unsupported encryption type"); } - if (mac != null) { - p.mac = Utils.fromB64ToArray(mac); - } - - return p; } async aesDecryptFast({ diff --git a/libs/tools/export/vault-export/vault-export-core/src/services/base-vault-export.service.ts b/libs/tools/export/vault-export/vault-export-core/src/services/base-vault-export.service.ts index 0a92f4f02d7..9a64298ffba 100644 --- a/libs/tools/export/vault-export/vault-export-core/src/services/base-vault-export.service.ts +++ b/libs/tools/export/vault-export/vault-export-core/src/services/base-vault-export.service.ts @@ -4,6 +4,7 @@ import { PinServiceAbstraction } from "@bitwarden/auth/common"; import { CryptoFunctionService } from "@bitwarden/common/key-management/crypto/abstractions/crypto-function.service"; import { EncryptService } from "@bitwarden/common/key-management/crypto/abstractions/encrypt.service"; import { Utils } from "@bitwarden/common/platform/misc/utils"; +import { UserId } from "@bitwarden/common/types/guid"; import { CipherType } from "@bitwarden/common/vault/enums"; import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view"; import { KdfConfig, KdfConfigService, KdfType } from "@bitwarden/key-management"; @@ -17,14 +18,18 @@ export class BaseVaultExportService { private kdfConfigService: KdfConfigService, ) {} - protected async buildPasswordExport(clearText: string, password: string): Promise { - const kdfConfig: KdfConfig = await this.kdfConfigService.getKdfConfig(); + protected async buildPasswordExport( + userId: UserId, + clearText: string, + password: string, + ): Promise { + const kdfConfig: KdfConfig = await this.kdfConfigService.getKdfConfig(userId); const salt = Utils.fromBufferToB64(await this.cryptoFunctionService.randomBytes(16)); const key = await this.pinService.makePinKey(password, salt, kdfConfig); - const encKeyValidation = await this.encryptService.encrypt(Utils.newGuid(), key); - const encText = await this.encryptService.encrypt(clearText, key); + const encKeyValidation = await this.encryptService.encryptString(Utils.newGuid(), key); + const encText = await this.encryptService.encryptString(clearText, key); const jsonDoc: BitwardenPasswordProtectedFileFormat = { encrypted: true, diff --git a/libs/tools/export/vault-export/vault-export-core/src/services/individual-vault-export.service.spec.ts b/libs/tools/export/vault-export/vault-export-core/src/services/individual-vault-export.service.spec.ts index 15791ae04fb..ae408af421b 100644 --- a/libs/tools/export/vault-export/vault-export-core/src/services/individual-vault-export.service.spec.ts +++ b/libs/tools/export/vault-export/vault-export-core/src/services/individual-vault-export.service.spec.ts @@ -209,7 +209,7 @@ describe("VaultExportService", () => { folderService.folderViews$.mockReturnValue(of(UserFolderViews)); folderService.folders$.mockReturnValue(of(UserFolders)); kdfConfigService.getKdfConfig.mockResolvedValue(DEFAULT_KDF_CONFIG); - encryptService.encrypt.mockResolvedValue(new EncString("encrypted")); + encryptService.encryptString.mockResolvedValue(new EncString("encrypted")); apiService.getAttachmentData.mockResolvedValue(attachmentResponse); exportService = new IndividualVaultExportService( @@ -313,7 +313,7 @@ describe("VaultExportService", () => { cipherService.getAllDecrypted.mockResolvedValue([cipherView]); folderService.getAllDecryptedFromState.mockResolvedValue([]); - encryptService.decryptToBytes.mockResolvedValue(new Uint8Array(255)); + encryptService.decryptFileData.mockResolvedValue(new Uint8Array(255)); global.fetch = jest.fn(() => Promise.resolve({ @@ -338,7 +338,7 @@ describe("VaultExportService", () => { cipherService.getAllDecrypted.mockResolvedValue([cipherView]); folderService.getAllDecryptedFromState.mockResolvedValue([]); - encryptService.decryptToBytes.mockResolvedValue(new Uint8Array(255)); + encryptService.decryptFileData.mockResolvedValue(new Uint8Array(255)); global.fetch = jest.fn(() => Promise.resolve({ @@ -362,7 +362,7 @@ describe("VaultExportService", () => { cipherView.attachments = [attachmentView]; cipherService.getAllDecrypted.mockResolvedValue([cipherView]); folderService.getAllDecryptedFromState.mockResolvedValue([]); - encryptService.decryptToBytes.mockResolvedValue(new Uint8Array(255)); + encryptService.decryptFileData.mockResolvedValue(new Uint8Array(255)); global.fetch = jest.fn(() => Promise.resolve({ status: 200, @@ -427,7 +427,7 @@ describe("VaultExportService", () => { }); it("has a mac property", async () => { - encryptService.encrypt.mockResolvedValue(mac); + encryptService.encryptString.mockResolvedValue(mac); exportedVault = await exportService.getPasswordProtectedExport(password); exportString = exportedVault.data; exportObject = JSON.parse(exportString); @@ -436,7 +436,7 @@ describe("VaultExportService", () => { }); it("has data property", async () => { - encryptService.encrypt.mockResolvedValue(data); + encryptService.encryptString.mockResolvedValue(data); exportedVault = await exportService.getPasswordProtectedExport(password); exportString = exportedVault.data; exportObject = JSON.parse(exportString); diff --git a/libs/tools/export/vault-export/vault-export-core/src/services/individual-vault-export.service.ts b/libs/tools/export/vault-export/vault-export-core/src/services/individual-vault-export.service.ts index d253ae8d0b1..8b66580d4cd 100644 --- a/libs/tools/export/vault-export/vault-export-core/src/services/individual-vault-export.service.ts +++ b/libs/tools/export/vault-export/vault-export-core/src/services/individual-vault-export.service.ts @@ -13,6 +13,7 @@ import { EncryptService } from "@bitwarden/common/key-management/crypto/abstract import { CipherWithIdExport, FolderWithIdExport } from "@bitwarden/common/models/export"; import { Utils } from "@bitwarden/common/platform/misc/utils"; import { EncArrayBuffer } from "@bitwarden/common/platform/models/domain/enc-array-buffer"; +import { UserId } from "@bitwarden/common/types/guid"; import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service"; import { FolderService } from "@bitwarden/common/vault/abstractions/folder/folder.service.abstraction"; import { CipherType } from "@bitwarden/common/vault/enums"; @@ -59,19 +60,21 @@ export class IndividualVaultExportService * @param format The format of the export */ async getExport(format: ExportFormat = "csv"): Promise { + const userId = await firstValueFrom(this.accountService.activeAccount$.pipe(getUserId)); if (format === "encrypted_json") { - return this.getEncryptedExport(); + return this.getEncryptedExport(userId); } else if (format === "zip") { - return this.getDecryptedExportZip(); + return this.getDecryptedExportZip(userId); } - return this.getDecryptedExport(format); + return this.getDecryptedExport(userId, format); } - /** Creates a password protected export of an individiual vault (My Vault) as a JSON file + /** Creates a password protected export of an individual vault (My Vault) as a JSON file * @param password The password to encrypt the export with * @returns A password-protected encrypted individual vault export */ async getPasswordProtectedExport(password: string): Promise { + const userId = await firstValueFrom(this.accountService.activeAccount$.pipe(getUserId)); const exportVault = await this.getExport("json"); if (exportVault.type !== "text/plain") { @@ -80,19 +83,20 @@ export class IndividualVaultExportService return { type: "text/plain", - data: await this.buildPasswordExport(exportVault.data, password), + data: await this.buildPasswordExport(userId, exportVault.data, password), fileName: ExportHelper.getFileName("", "encrypted_json"), } as ExportedVaultAsString; } /** Creates a unencrypted export of an individual vault including attachments + * @param activeUserId The user ID of the user requesting the export * @returns A unencrypted export including attachments */ - async getDecryptedExportZip(): Promise { + async getDecryptedExportZip(activeUserId: UserId): Promise { const zip = new JSZip(); // ciphers - const exportedVault = await this.getDecryptedExport("json"); + const exportedVault = await this.getDecryptedExport(activeUserId, "json"); zip.file("data.json", exportedVault.data); const attachmentsFolder = zip.folder("attachments"); @@ -100,8 +104,6 @@ export class IndividualVaultExportService throw new Error("Error creating attachments folder"); } - const activeUserId = await firstValueFrom(this.accountService.activeAccount$.pipe(getUserId)); - // attachments for (const cipher of await this.cipherService.getAllDecrypted(activeUserId)) { if ( @@ -155,17 +157,19 @@ export class IndividualVaultExportService attachment.key != null ? attachment.key : await this.keyService.getOrgKey(cipher.organizationId); - return await this.encryptService.decryptToBytes(encBuf, key); + return await this.encryptService.decryptFileData(encBuf, key); } catch { throw new Error("Error decrypting attachment"); } } - private async getDecryptedExport(format: "json" | "csv"): Promise { + private async getDecryptedExport( + activeUserId: UserId, + format: "json" | "csv", + ): Promise { let decFolders: FolderView[] = []; let decCiphers: CipherView[] = []; const promises = []; - const activeUserId = await firstValueFrom(this.accountService.activeAccount$.pipe(getUserId)); promises.push( firstValueFrom(this.folderService.folderViews$(activeUserId)).then((folders) => { @@ -196,11 +200,10 @@ export class IndividualVaultExportService } as ExportedVaultAsString; } - private async getEncryptedExport(): Promise { + private async getEncryptedExport(activeUserId: UserId): Promise { let folders: Folder[] = []; let ciphers: Cipher[] = []; const promises = []; - const activeUserId = await firstValueFrom(this.accountService.activeAccount$.pipe(getUserId)); promises.push( firstValueFrom(this.folderService.folders$(activeUserId)).then((f) => { @@ -216,10 +219,8 @@ export class IndividualVaultExportService await Promise.all(promises); - const userKey = await this.keyService.getUserKeyWithLegacySupport( - await firstValueFrom(this.accountService.activeAccount$.pipe(getUserId)), - ); - const encKeyValidation = await this.encryptService.encrypt(Utils.newGuid(), userKey); + const userKey = await this.keyService.getUserKeyWithLegacySupport(activeUserId); + const encKeyValidation = await this.encryptService.encryptString(Utils.newGuid(), userKey); const jsonDoc: BitwardenEncryptedIndividualJsonExport = { encrypted: true, diff --git a/libs/tools/export/vault-export/vault-export-core/src/services/org-vault-export.service.ts b/libs/tools/export/vault-export/vault-export-core/src/services/org-vault-export.service.ts index e4ed105d1ad..fc46915c15d 100644 --- a/libs/tools/export/vault-export/vault-export-core/src/services/org-vault-export.service.ts +++ b/libs/tools/export/vault-export/vault-export-core/src/services/org-vault-export.service.ts @@ -18,7 +18,7 @@ import { CryptoFunctionService } from "@bitwarden/common/key-management/crypto/a import { EncryptService } from "@bitwarden/common/key-management/crypto/abstractions/encrypt.service"; import { CipherWithIdExport, CollectionWithIdExport } from "@bitwarden/common/models/export"; import { Utils } from "@bitwarden/common/platform/misc/utils"; -import { OrganizationId } from "@bitwarden/common/types/guid"; +import { OrganizationId, UserId } from "@bitwarden/common/types/guid"; import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service"; import { CipherType } from "@bitwarden/common/vault/enums"; import { CipherData } from "@bitwarden/common/vault/models/data/cipher.data"; @@ -67,6 +67,7 @@ export class OrganizationVaultExportService password: string, onlyManagedCollections: boolean, ): Promise { + const userId = await firstValueFrom(this.accountService.activeAccount$.pipe(getUserId)); const exportVault = await this.getOrganizationExport( organizationId, "json", @@ -75,7 +76,7 @@ export class OrganizationVaultExportService return { type: "text/plain", - data: await this.buildPasswordExport(exportVault.data, password), + data: await this.buildPasswordExport(userId, exportVault.data, password), fileName: ExportHelper.getFileName("org", "encrypted_json"), } as ExportedVaultAsString; } @@ -102,12 +103,13 @@ export class OrganizationVaultExportService if (format === "zip") { throw new Error("Zip export not supported for organization"); } + const userId = await firstValueFrom(this.accountService.activeAccount$.pipe(getUserId)); if (format === "encrypted_json") { return { type: "text/plain", data: onlyManagedCollections - ? await this.getEncryptedManagedExport(organizationId) + ? await this.getEncryptedManagedExport(userId, organizationId) : await this.getOrganizationEncryptedExport(organizationId), fileName: ExportHelper.getFileName("org", "encrypted_json"), } as ExportedVaultAsString; @@ -116,20 +118,20 @@ export class OrganizationVaultExportService return { type: "text/plain", data: onlyManagedCollections - ? await this.getDecryptedManagedExport(organizationId, format) - : await this.getOrganizationDecryptedExport(organizationId, format), + ? await this.getDecryptedManagedExport(userId, organizationId, format) + : await this.getOrganizationDecryptedExport(userId, organizationId, format), fileName: ExportHelper.getFileName("org", format), } as ExportedVaultAsString; } private async getOrganizationDecryptedExport( + activeUserId: UserId, organizationId: string, format: "json" | "csv", ): Promise { const decCollections: CollectionView[] = []; const decCiphers: CipherView[] = []; const promises = []; - const activeUserId = await firstValueFrom(this.accountService.activeAccount$.pipe(getUserId)); promises.push( this.apiService.getOrganizationExport(organizationId).then((exportData) => { @@ -210,6 +212,7 @@ export class OrganizationVaultExportService } private async getDecryptedManagedExport( + activeUserId: UserId, organizationId: string, format: "json" | "csv", ): Promise
- “Bitwarden scores points for being fully open-source, secure and audited annually by third-party - cybersecurity firms, giving it a level of transparency that sets it apart from its peers.” -
Best Password Manager in 2024
- "No more excuses; start using Bitwarden today. The identity you save could be your own. The - money definitely will be." -
- Recommended by industry experts -
- “Bitwarden is currently CNET's top pick for the best password manager, thanks in part to - its commitment to transparency and its unbeatable free tier.” -
- “Bitwarden boasts the backing of some of the world's best security experts and an attractive, - easy-to-use interface” -
- "{{ quote }}" -
{{ source }}
{{ "smFreeTrialThankYou" | i18n }}
- {{ "smFreeTrialConfirmationEmail" | i18n }} - {{ formGroup.get("email").value }}. -
- -
+ +
{{ "providerInviteUserDesc" | i18n }}
{{ "setupProviderDesc" | i18n }}