diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 2f402b15dd5..df099efa1d6 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -8,7 +8,8 @@ apps/desktop/desktop_native @bitwarden/team-platform-dev apps/desktop/desktop_native/objc/src/native/autofill @bitwarden/team-autofill-dev apps/desktop/desktop_native/core/src/autofill @bitwarden/team-autofill-dev -## No ownership for Cargo.toml to allow dependency updates +## No ownership fo Cargo.lock and Cargo.toml to allow dependency updates +apps/desktop/desktop_native/Cargo.lock apps/desktop/desktop_native/Cargo.toml ## Auth team files ## @@ -33,8 +34,12 @@ libs/common/src/models/export @bitwarden/team-tools-dev libs/common/src/tools @bitwarden/team-tools-dev libs/importer @bitwarden/team-tools-dev libs/tools @bitwarden/team-tools-dev -bitwarden_license/bit-web/src/app/tools @bitwarden/team-tools-dev -bitwarden_license/bit-common/src/tools @bitwarden/team-tools-dev + +## Dirt (Data Insights & Reporting) team files ## +apps/web/src/app/dirt @bitwarden/team-data-insights-and-reporting-dev +bitwarden_license/bit-common/src/dirt @bitwarden/team-data-insights-and-reporting-dev +bitwarden_license/bit-web/src/app/dirt @bitwarden/team-data-insights-and-reporting-dev +libs/dirt @bitwarden/team-data-insights-and-reporting-dev ## Localization/Crowdin (Platform and Tools team) apps/browser/src/_locales @bitwarden/team-tools-dev @bitwarden/team-platform-dev @@ -90,7 +95,6 @@ apps/web/src/app/core @bitwarden/team-platform-dev apps/web/src/app/shared @bitwarden/team-platform-dev apps/web/src/translation-constants.ts @bitwarden/team-platform-dev # Workflows -# Any changes here should also be reflected in Renovate configuration .github/workflows/automatic-issue-responses.yml @bitwarden/team-platform-dev .github/workflows/automatic-pull-request-responses.yml @bitwarden/team-platform-dev .github/workflows/build-browser-target.yml @bitwarden/team-platform-dev @@ -113,6 +117,9 @@ apps/web/src/translation-constants.ts @bitwarden/team-platform-dev .github/workflows/version-auto-bump.yml @bitwarden/team-platform-dev # ESLint custom rules libs/eslint @bitwarden/team-platform-dev +# Typescript tooling +tsconfig.base.json @bitwarden/team-platform-dev +nx.json @bitwarden/team-platform-dev ## Autofill team files ## apps/browser/src/autofill @bitwarden/team-autofill-dev @@ -160,7 +167,6 @@ apps/desktop/src/locales/en/messages.json apps/web/src/locales/en/messages.json ## BRE team owns these workflows ## -# Any changes here should also be reflected in Renovate configuration ## .github/workflows/brew-bump-desktop.yml @bitwarden/dept-bre .github/workflows/deploy-web.yml @bitwarden/dept-bre .github/workflows/publish-cli.yml @bitwarden/dept-bre @@ -181,5 +187,9 @@ apps/web/src/locales/en/messages.json **/entrypoint.sh ## Overrides -# tsconfig files are potentially dangerous and will be reviewed by platform to prevent misconfigurations +# For the time being platform owns tsconfig and jest config +# These overrides will be removed after Nx is implemented +# To track that effort please see https://bitwarden.atlassian.net/browse/PM-21636 **/tsconfig.json @bitwarden/team-platform-dev +**/jest.config.js @bitwarden/team-platform-dev +**/project.jsons @bitwarden/team-platform-dev diff --git a/.github/ISSUE_TEMPLATE/desktop.yml b/.github/ISSUE_TEMPLATE/desktop.yml index 6fd6f1d1c2b..129ba510664 100644 --- a/.github/ISSUE_TEMPLATE/desktop.yml +++ b/.github/ISSUE_TEMPLATE/desktop.yml @@ -73,6 +73,7 @@ body: - Homebrew - Chocolatey - Snap + - Flatpak - Other validations: required: true diff --git a/.github/codecov.yml b/.github/codecov.yml index d9d59f9de28..ba4c4b48163 100644 --- a/.github/codecov.yml +++ b/.github/codecov.yml @@ -44,6 +44,7 @@ component_management: - component_id: key-management-keys name: Key Management - Keys paths: + - apps/desktop/src/key-management/electron-key.service.ts - libs/key-management/src/kdf-config.service.ts - libs/key-management/src/key.service.ts - libs/common/src/key-management/master-password/** diff --git a/.github/renovate.json5 b/.github/renovate.json5 index c4202ed2a68..09afb97174f 100644 --- a/.github/renovate.json5 +++ b/.github/renovate.json5 @@ -4,51 +4,24 @@ enabledManagers: ["cargo", "github-actions", "npm"], packageRules: [ { - // Group all build/test/lint workflows for GitHub Actions together for Platform. - // Since they are code owners we don't need to assign a review team in Renovate. - // Any changes here should also be reflected in CODEOWNERS. - groupName: "github-action", + // Group all Github Action minor updates together to reduce PR noise. + groupName: "Minor github-actions updates", matchManagers: ["github-actions"], - matchFileNames: [ - "./github/workflows/automatic-issue-responses.yml", - "./github/workflows/automatic-pull-request-responses.yml", - "./github/workflows/build-browser.yml", - "./github/workflows/build-cli.yml", - "./github/workflows/build-desktop.yml", - "./github/workflows/build-web.yml", - "./github/workflows/chromatic.yml", - "./github/workflows/crowdin-pull.yml", - "./github/workflows/enforce-labels.yml", - "./github/workflows/lint.yml", - "./github/workflows/locales-lint.yml", - "./github/workflows/repository-management.yml", - "./github/workflows/scan.yml", - "./github/workflows/stale-bot.yml", - "./github/workflows/test.yml", - "./github/workflows/version-auto-bump.yml", - ], - commitMessagePrefix: "[deps] Platform:", + matchUpdateTypes: ["minor"], + addLabels: ["hold"], }, { - // Group all release-related workflows for GitHub Actions together for BRE. - // Since they are code owners we don't need to assign a review team in Renovate. - // Any changes here should also be reflected in CODEOWNERS. - groupName: "github-action", - matchManagers: ["github-actions"], - matchFileNames: [ - "./github/workflows/brew-bump-desktop.yml", - "./github/workflows/deploy-web.yml", - "./github/workflows/publish-cli.yml", - "./github/workflows/publish-desktop.yml", - "./github/workflows/publish-web.yml", - "./github/workflows/retrieve-current-desktop-rollout.yml", - "./github/workflows/staged-rollout-desktop.yml", - "./github/workflows/release-cli.yml", - "./github/workflows/release-desktop-beta.yml", - "./github/workflows/release-desktop.yml", - "./github/workflows/release-web.yml", - ], - commitMessagePrefix: "[deps] BRE:", + // Enable support for Rust toolchain updates. + matchManagers: ["custom.regex"], + matchDepNames: ["rust"], + commitMessageTopic: "Rust", + }, + { + // By default, we send patch updates to the Dependency Dashboard and do not generate a PR. + // We want to generate PRs for a select number of dependencies to ensure we stay up to date on these. + matchPackageNames: ["browserslist", "electron", "rxjs", "typescript", "webpack", "zone.js"], + matchUpdateTypes: ["patch"], + dependencyDashboardApproval: false, }, { // Disable major and minor updates for TypeScript and Zone.js because they are managed by Angular. @@ -72,49 +45,7 @@ enabled: false, }, { - // Renovate should manage patch updates for TypeScript and Zone.js, despite ignoring major and minor. - matchPackageNames: ["typescript", "zone.js"], - matchUpdateTypes: "patch", - }, - { - // We want to update all the Jest-related packages together, to reduce PR noise. - groupName: "jest", - matchPackageNames: ["@types/jest", "jest", "ts-jest", "jest-preset-angular"], - }, - { - // We need to group all napi-related packages together to avoid build errors caused by version incompatibilities. - groupName: "napi", - matchPackageNames: ["napi", "napi-build", "napi-derive"], - }, - { - // We need to group all macOS/iOS binding-related packages together to avoid build errors caused by version incompatibilities. - groupName: "macOS/iOS bindings", - matchPackageNames: ["core-foundation", "security-framework", "security-framework-sys"], - }, - { - // We need to group all zbus-related packages together to avoid build errors caused by version incompatibilities. - groupName: "zbus", - matchPackageNames: ["zbus", "zbus_polkit"], - }, - { - matchPackageNames: [ - "base64-loader", - "buffer", - "bufferutil", - "core-js", - "css-loader", - "html-loader", - "mini-css-extract-plugin", - "postcss", - "postcss-loader", - "process", - "sass", - "sass-loader", - "style-loader", - "ts-loader", - "url", - "util", - ], + matchPackageNames: ["buffer", "bufferutil", "core-js", "process", "url", "util"], description: "Admin Console owned dependencies", commitMessagePrefix: "[deps] AC:", reviewers: ["team:team-admin-console-dev"], @@ -128,6 +59,9 @@ { matchPackageNames: [ "@angular-eslint/schematics", + "@eslint/compat", + "@typescript-eslint/rule-tester", + "@typescript-eslint/utils", "angular-eslint", "eslint-config-prettier", "eslint-import-resolver-typescript", @@ -148,6 +82,9 @@ { matchPackageNames: [ "@angular-eslint/schematics", + "@eslint/compat", + "@typescript-eslint/rule-tester", + "@typescript-eslint/utils", "angular-eslint", "eslint-config-prettier", "eslint-import-resolver-typescript", @@ -161,7 +98,7 @@ "lint-staged", "typescript-eslint", ], - groupName: "Linting minor-patch", + groupName: "Minor and patch linting updates", matchUpdateTypes: ["minor", "patch"], }, { @@ -207,10 +144,13 @@ "@electron/notarize", "@electron/rebuild", "@ngtools/webpack", + "@nx/devkit", + "@nx/eslint", + "@nx/jest", + "@nx/js", "@types/chrome", "@types/firefox-webext-browser", "@types/glob", - "@types/jquery", "@types/lowdb", "@types/node", "@types/node-forge", @@ -219,6 +159,7 @@ "anyhow", "arboard", "babel-loader", + "base64-loader", "base64", "bindgen", "browserslist", @@ -226,6 +167,7 @@ "bytes", "core-foundation", "copy-webpack-plugin", + "css-loader", "dirs", "electron", "electron-builder", @@ -237,6 +179,7 @@ "futures", "hex", "homedir", + "html-loader", "html-webpack-injector", "html-webpack-plugin", "interprocess", @@ -245,6 +188,7 @@ "libc", "log", "lowdb", + "mini-css-extract-plugin", "napi", "napi-build", "napi-derive", @@ -255,15 +199,23 @@ "oslog", "pin-project", "pkg", + "postcss", + "postcss-loader", "rand", "rxjs", + "sass", + "sass-loader", "scopeguard", "security-framework", "security-framework-sys", + "semver", "serde", "serde_json", "simplelog", + "style-loader", "sysinfo", + "ts-node", + "ts-loader", "tsconfig-paths-webpack-plugin", "type-fest", "typenum", @@ -285,6 +237,52 @@ commitMessagePrefix: "[deps] Platform:", reviewers: ["team:team-platform-dev"], }, + { + // We need to group all napi-related packages together to avoid build errors caused by version incompatibilities. + groupName: "napi", + matchPackageNames: ["napi", "napi-build", "napi-derive"], + }, + { + // We need to group all macOS/iOS binding-related packages together to avoid build errors caused by version incompatibilities. + groupName: "macOS/iOS bindings", + matchPackageNames: ["core-foundation", "security-framework", "security-framework-sys"], + }, + { + // We need to group all zbus-related packages together to avoid build errors caused by version incompatibilities. + groupName: "zbus", + matchPackageNames: ["zbus", "zbus_polkit"], + }, + { + // We group all webpack build-related minor and patch updates together to reduce PR noise. + // We include patch updates here because we want PRs for webpack patch updates and it's in this group. + matchPackageNames: [ + "@babel/core", + "@babel/preset-env", + "babel-loader", + "base64-loader", + "browserslist", + "copy-webpack-plugin", + "css-loader", + "html-loader", + "html-webpack-injector", + "html-webpack-plugin", + "mini-css-extract-plugin", + "postcss-loader", + "postcss", + "sass-loader", + "sass", + "style-loader", + "ts-loader", + "tsconfig-paths-webpack-plugin", + "webpack-cli", + "webpack-dev-server", + "webpack-node-externals", + "webpack", + ], + description: "webpack-related build dependencies", + groupName: "Minor and patch webpack updates", + matchUpdateTypes: ["minor", "patch"], + }, { matchPackageNames: [ "@angular-devkit/build-angular", @@ -300,6 +298,7 @@ "@angular/platform-browser", "@angular/platform", "@angular/router", + "axe-playwright", "@compodoc/compodoc", "@ng-select/ng-select", "@storybook/addon-a11y", @@ -308,19 +307,16 @@ "@storybook/addon-essentials", "@storybook/addon-interactions", "@storybook/addon-links", + "@storybook/test-runner", "@storybook/addon-themes", "@storybook/angular", "@storybook/manager-api", "@storybook/theming", - "@typescript-eslint/utils", - "@typescript-eslint/rule-tester", "@types/react", "autoprefixer", "bootstrap", "chromatic", - "jquery", "ngx-toastr", - "popper.js", "react", "react-dom", "remark-gfm", @@ -345,6 +341,11 @@ commitMessagePrefix: "[deps] SM:", reviewers: ["team:team-secrets-manager-dev"], }, + { + // We need to update several Jest-related packages together, for version compatibility. + groupName: "jest", + matchPackageNames: ["@types/jest", "jest", "ts-jest", "jest-preset-angular"], + }, { matchPackageNames: [ "@microsoft/signalr-protocol-msgpack", @@ -412,6 +413,18 @@ commitMessagePrefix: "[deps] KM:", reviewers: ["team:team-key-management-dev"], }, + { + // Any versions of lowdb above 1.0.0 are not compatible with CommonJS. + matchPackageNames: ["lowdb"], + allowedVersions: "1.0.0", + description: "Higher versions of lowdb are not compatible with CommonJS", + }, + { + // Pin types as well since we are not upgrading past v1 (and also v2+ does not need separate types). + matchPackageNames: ["@types/lowdb"], + allowedVersions: "< 2.0.0", + description: "Higher versions of lowdb do not need separate types", + }, ], - ignoreDeps: ["@types/koa-bodyparser", "bootstrap", "node-ipc", "node", "npm"], + ignoreDeps: ["@types/koa-bodyparser", "bootstrap", "node-ipc", "@bitwarden/sdk-internal"], } diff --git a/.github/whitelist-capital-letters.txt b/.github/whitelist-capital-letters.txt index 653f6591c7f..db5097e5268 100644 --- a/.github/whitelist-capital-letters.txt +++ b/.github/whitelist-capital-letters.txt @@ -34,3 +34,4 @@ ./apps/browser/src/safari/safari/Info.plist ./apps/browser/src/safari/desktop.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist ./SECURITY.md +./libs/nx-plugin/src/generators/files/README.md__tmpl__ diff --git a/.github/workflows/build-browser-target.yml b/.github/workflows/build-browser-target.yml index 6f05cb71934..a2ae48d419b 100644 --- a/.github/workflows/build-browser-target.yml +++ b/.github/workflows/build-browser-target.yml @@ -8,10 +8,9 @@ name: Build Browser on PR Target on: pull_request_target: - types: [opened, synchronize] - branches-ignore: - - 'l10n_master' - - 'cf-pages' + types: [opened, synchronize, reopened] + branches: + - main paths: - 'apps/browser/**' - 'libs/**' diff --git a/.github/workflows/build-browser.yml b/.github/workflows/build-browser.yml index f7b8eeabefe..0b44cd1c4af 100644 --- a/.github/workflows/build-browser.yml +++ b/.github/workflows/build-browser.yml @@ -41,6 +41,8 @@ defaults: run: shell: bash +permissions: {} + jobs: setup: name: Setup diff --git a/.github/workflows/build-cli-target.yml b/.github/workflows/build-cli-target.yml index f817046ff30..6b493d4e6d9 100644 --- a/.github/workflows/build-cli-target.yml +++ b/.github/workflows/build-cli-target.yml @@ -8,10 +8,9 @@ name: Build CLI on PR Target on: pull_request_target: - types: [opened, synchronize] - branches-ignore: - - 'l10n_master' - - 'cf-pages' + types: [opened, synchronize, reopened] + branches: + - main paths: - 'apps/cli/**' - 'libs/**' diff --git a/.github/workflows/build-cli.yml b/.github/workflows/build-cli.yml index 8af6371dc69..ee0df773de1 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: @@ -44,6 +46,9 @@ defaults: run: working-directory: apps/cli +permissions: + contents: read + jobs: setup: name: Setup @@ -231,6 +236,21 @@ jobs: path: apps/cli/dist/bw${{ matrix.license_type.artifact_prefix }}-${{ env.LOWER_RUNNER_OS }}${{ matrix.os.target_suffix }}-${{ env._PACKAGE_VERSION }}.zip if-no-files-found: error + # We want to confirm the CLI is runnable using the dependencies defined in `apps/cli/package.json`. + - name: Remove node_modules (root) + run: rm -rf node_modules + working-directory: ./ + + - name: Remove package.json (root) + run: rm package.json + working-directory: ./ + + - name: Install (CLI) + run: npm i + + - name: Output help + run: node ./build/bw.js --help + cli-windows: name: Windows - ${{ matrix.license_type.readable }} @@ -406,11 +426,6 @@ jobs: Throw "Version test failed." } - - name: Create checksums Windows - run: | - checksum -f="./dist/bw${{ matrix.license_type.artifact_prefix }}-windows-${env:_PACKAGE_VERSION}.zip" ` - -t sha256 | Out-File -Encoding ASCII ./dist/bw${{ matrix.license_type.artifact_prefix }}-windows-sha256-${env:_PACKAGE_VERSION}.txt - - name: Upload windows zip asset uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4.6.0 with: @@ -418,13 +433,6 @@ jobs: path: apps/cli/dist/bw${{ matrix.license_type.artifact_prefix }}-windows-${{ env._PACKAGE_VERSION }}.zip if-no-files-found: error - - name: Upload windows checksum asset - uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4.6.0 - with: - name: bw${{ matrix.license_type.artifact_prefix }}-windows-sha256-${{ env._PACKAGE_VERSION }}.txt - path: apps/cli/dist/bw${{ matrix.license_type.artifact_prefix }}-windows-sha256-${{ env._PACKAGE_VERSION }}.txt - if-no-files-found: error - - name: Upload Chocolatey asset if: matrix.license_type.build_prefix == 'bit' uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4.6.0 @@ -484,13 +492,6 @@ jobs: with: path: apps/cli/dist/snap - - name: Create checksum - run: | - cd dist/snap - ls -alth - sha256sum bw_${{ env._PACKAGE_VERSION }}_amd64.snap \ - | awk '{split($0, a); print a[1]}' > bw-snap-sha256-${{ env._PACKAGE_VERSION }}.txt - - name: Install Snap run: sudo snap install dist/snap/bw*.snap --dangerous @@ -515,13 +516,6 @@ jobs: path: apps/cli/dist/snap/bw_${{ env._PACKAGE_VERSION }}_amd64.snap if-no-files-found: error - - name: Upload snap checksum asset - uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4.6.0 - with: - name: bw-snap-sha256-${{ env._PACKAGE_VERSION }}.txt - path: apps/cli/dist/snap/bw-snap-sha256-${{ env._PACKAGE_VERSION }}.txt - if-no-files-found: error - check-failures: name: Check for failures diff --git a/.github/workflows/build-desktop-target.yml b/.github/workflows/build-desktop-target.yml index 65772223722..fa21b3fe5d9 100644 --- a/.github/workflows/build-desktop-target.yml +++ b/.github/workflows/build-desktop-target.yml @@ -9,10 +9,9 @@ name: Build Desktop on PR Target on: pull_request_target: - types: [opened, synchronize] - branches-ignore: - - 'l10n_master' - - 'cf-pages' + types: [opened, synchronize, reopened] + branches: + - main paths: - 'apps/desktop/**' - 'libs/**' diff --git a/.github/workflows/build-desktop.yml b/.github/workflows/build-desktop.yml index 365d29f17f7..692331af60d 100644 --- a/.github/workflows/build-desktop.yml +++ b/.github/workflows/build-desktop.yml @@ -46,6 +46,9 @@ defaults: run: shell: bash +permissions: + contents: read + jobs: electron-verify: name: Verify Electron Version @@ -92,6 +95,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 @@ -424,15 +428,9 @@ jobs: - name: Install AST run: dotnet tool install --global AzureSignTool --version 4.0.1 - - name: Set up environmentF + - name: Set up environment run: choco install checksum --no-progress - - name: Rust - shell: pwsh - run: | - rustup target install i686-pc-windows-msvc - rustup target install aarch64-pc-windows-msvc - - name: Print environment run: | node --version @@ -680,10 +678,6 @@ jobs: - name: Set up Node-gyp run: python3 -m pip install setuptools - - name: Rust - shell: pwsh - run: rustup target install aarch64-apple-darwin - - name: Print environment run: | node --version @@ -725,6 +719,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 +783,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: @@ -875,10 +883,6 @@ jobs: - name: Set up Node-gyp run: python3 -m pip install setuptools - - name: Rust - shell: pwsh - run: rustup target install aarch64-apple-darwin - - name: Print environment run: | node --version @@ -914,8 +918,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 +967,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 @@ -1117,10 +1126,6 @@ jobs: - name: Set up Node-gyp run: python3 -m pip install setuptools - - name: Rust - shell: pwsh - run: rustup target install aarch64-apple-darwin - - name: Print environment run: | node --version @@ -1167,6 +1172,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 +1211,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 +1224,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: @@ -1378,226 +1388,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-target.yml b/.github/workflows/build-web-target.yml index 8f06d066d34..ca10e6d46f2 100644 --- a/.github/workflows/build-web-target.yml +++ b/.github/workflows/build-web-target.yml @@ -8,10 +8,9 @@ name: Build Web on PR Target on: pull_request_target: - types: [opened, synchronize] - branches-ignore: - - 'l10n_master' - - 'cf-pages' + types: [opened, synchronize, reopened] + branches: + - main paths: - 'apps/web/**' - 'libs/**' diff --git a/.github/workflows/build-web.yml b/.github/workflows/build-web.yml index 3da524702fe..e44449bdbeb 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' @@ -47,6 +51,8 @@ env: _AZ_REGISTRY: bitwardenprod.azurecr.io _GITHUB_PR_REPO_NAME: ${{ github.event.pull_request.head.repo.full_name }} +permissions: {} + jobs: setup: name: Setup @@ -129,12 +135,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 +184,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 +198,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 @@ -265,7 +293,7 @@ jobs: - name: Install Cosign if: github.event_name != 'pull_request_target' && github.ref == 'refs/heads/main' - uses: sigstore/cosign-installer@dc72c7d5c4d10cd6bcb8cf6e3fd625a9e5e537da # v3.7.0 + uses: sigstore/cosign-installer@3454372f43399081ed03b604cb2d021dabca52bb # v3.8.2 - name: Sign image with Cosign if: github.event_name != 'pull_request_target' && github.ref == 'refs/heads/main' @@ -283,7 +311,7 @@ jobs: - name: Scan Docker image if: ${{ needs.setup.outputs.has_secrets == 'true' }} id: container-scan - uses: anchore/scan-action@869c549e657a088dc0441b08ce4fc0ecdac2bb65 # v5.3.0 + uses: anchore/scan-action@2c901ab7378897c01b8efaa2d0c9bf519cc64b9e # v6.2.0 with: image: ${{ steps.image-name.outputs.name }} fail-build: false @@ -300,7 +328,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/.github/workflows/chromatic.yml b/.github/workflows/chromatic.yml index f436f1b3760..78733bc5a8b 100644 --- a/.github/workflows/chromatic.yml +++ b/.github/workflows/chromatic.yml @@ -7,7 +7,9 @@ on: - "rc" - "hotfix-rc" pull_request_target: - types: [opened, synchronize] + types: [opened, synchronize, reopened] + branches: + - "main" jobs: check-run: @@ -73,7 +75,7 @@ jobs: run: npm run build-storybook:ci - name: Publish to Chromatic - uses: chromaui/action@8a12962215a66cd05b1ac5b0f1c08768d1aab155 # v11.25.0 + uses: chromaui/action@e8cc4c31775280b175a3c440076c00d19a9014d7 # v11.28.2 with: token: ${{ secrets.GITHUB_TOKEN }} projectToken: ${{ secrets.CHROMATIC_PROJECT_TOKEN }} diff --git a/.github/workflows/crowdin-pull.yml b/.github/workflows/crowdin-pull.yml index 32907699747..2fc035ec038 100644 --- a/.github/workflows/crowdin-pull.yml +++ b/.github/workflows/crowdin-pull.yml @@ -22,7 +22,7 @@ jobs: crowdin_project_id: "308189" steps: - name: Generate GH App token - uses: actions/create-github-app-token@d72941d797fd3113feb6b93fd0dec494b13a2547 # v1.12.0 + uses: actions/create-github-app-token@30bf6253fa41bdc8d1501d202ad15287582246b4 # v2.0.3 id: app-token with: app-id: ${{ secrets.BW_GHAPP_ID }} diff --git a/.github/workflows/release-cli.yml b/.github/workflows/release-cli.yml index 519fee1989b..31a16dc9a6d 100644 --- a/.github/workflows/release-cli.yml +++ b/.github/workflows/release-cli.yml @@ -18,6 +18,9 @@ defaults: run: working-directory: apps/cli +permissions: + contents: read + jobs: setup: name: Setup @@ -78,24 +81,15 @@ jobs: PKG_VERSION: ${{ needs.setup.outputs.release_version }} with: artifacts: "apps/cli/bw-oss-windows-${{ env.PKG_VERSION }}.zip, - apps/cli/bw-oss-windows-sha256-${{ env.PKG_VERSION }}.txt, apps/cli/bw-windows-${{ env.PKG_VERSION }}.zip, - apps/cli/bw-windows-sha256-${{ env.PKG_VERSION }}.txt, apps/cli/bw-oss-macos-${{ env.PKG_VERSION }}.zip, - apps/cli/bw-oss-macos-sha256-${{ env.PKG_VERSION }}.txt, apps/cli/bw-oss-macos-arm64-${{ env.PKG_VERSION }}.zip, - apps/cli/bw-oss-macos-arm64-sha256-${{ env.PKG_VERSION }}.txt, apps/cli/bw-macos-${{ env.PKG_VERSION }}.zip, - apps/cli/bw-macos-sha256-${{ env.PKG_VERSION }}.txt, apps/cli/bw-macos-arm64-${{ env.PKG_VERSION }}.zip, - apps/cli/bw-macos-arm64-sha256-${{ env.PKG_VERSION }}.txt, apps/cli/bw-oss-linux-${{ env.PKG_VERSION }}.zip, - apps/cli/bw-oss-linux-sha256-${{ env.PKG_VERSION }}.txt, apps/cli/bw-linux-${{ env.PKG_VERSION }}.zip, - apps/cli/bw-linux-sha256-${{ env.PKG_VERSION }}.txt, apps/cli/bitwarden-cli.${{ env.PKG_VERSION }}.nupkg, apps/cli/bw_${{ env.PKG_VERSION }}_amd64.snap, - apps/cli/bw-snap-sha256-${{ env.PKG_VERSION }}.txt, apps/cli/bitwarden-cli-${{ env.PKG_VERSION }}-npm-build.zip" commit: ${{ github.sha }} tag: cli-v${{ env.PKG_VERSION }} diff --git a/.github/workflows/release-desktop.yml b/.github/workflows/release-desktop.yml index 57143747a86..b3c3fe5d250 100644 --- a/.github/workflows/release-desktop.yml +++ b/.github/workflows/release-desktop.yml @@ -17,6 +17,9 @@ defaults: run: shell: bash +permissions: + contents: read + jobs: setup: name: Setup @@ -89,12 +92,6 @@ jobs: working-directory: apps/desktop/artifacts run: mv Bitwarden-${{ env.PKG_VERSION }}-universal.pkg Bitwarden-${{ env.PKG_VERSION }}-universal.pkg.archive - - name: Get checksum files - uses: bitwarden/gh-actions/get-checksum@main - with: - packages_dir: "apps/desktop/artifacts" - file_path: "apps/desktop/artifacts/sha256-checksums.txt" - - name: Create Release uses: ncipollo/release-action@cdcc88a9acf3ca41c16c37bb7d21b9ad48560d87 # v1.15.0 if: ${{ steps.release_channel.outputs.channel == 'latest' && github.event.inputs.release_type != 'Dry Run' }} @@ -125,8 +122,7 @@ jobs: apps/desktop/artifacts/Bitwarden-${{ env.PKG_VERSION }}-universal.pkg.archive, apps/desktop/artifacts/${{ env.RELEASE_CHANNEL }}.yml, apps/desktop/artifacts/${{ env.RELEASE_CHANNEL }}-linux.yml, - apps/desktop/artifacts/${{ env.RELEASE_CHANNEL }}-mac.yml, - apps/desktop/artifacts/sha256-checksums.txt" + apps/desktop/artifacts/${{ env.RELEASE_CHANNEL }}-mac.yml" commit: ${{ github.sha }} tag: desktop-v${{ env.PKG_VERSION }} name: Desktop v${{ env.PKG_VERSION }} diff --git a/.github/workflows/repository-management.yml b/.github/workflows/repository-management.yml index 06daf70d7c9..8ab74adf543 100644 --- a/.github/workflows/repository-management.yml +++ b/.github/workflows/repository-management.yml @@ -66,7 +66,7 @@ jobs: runs-on: ubuntu-24.04 steps: - name: Generate GH App token - uses: actions/create-github-app-token@d72941d797fd3113feb6b93fd0dec494b13a2547 # v1.12.0 + uses: actions/create-github-app-token@30bf6253fa41bdc8d1501d202ad15287582246b4 # v2.0.3 id: app-token with: app-id: ${{ secrets.BW_GHAPP_ID }} @@ -115,7 +115,7 @@ jobs: version: ${{ inputs.version_number_override }} - name: Generate GH App token - uses: actions/create-github-app-token@d72941d797fd3113feb6b93fd0dec494b13a2547 # v1.12.0 + uses: actions/create-github-app-token@30bf6253fa41bdc8d1501d202ad15287582246b4 # v2.0.3 id: app-token with: app-id: ${{ secrets.BW_GHAPP_ID }} @@ -452,7 +452,7 @@ jobs: - setup steps: - name: Generate GH App token - uses: actions/create-github-app-token@d72941d797fd3113feb6b93fd0dec494b13a2547 # v1.12.0 + uses: actions/create-github-app-token@30bf6253fa41bdc8d1501d202ad15287582246b4 # v2.0.3 id: app-token with: app-id: ${{ secrets.BW_GHAPP_ID }} diff --git a/.github/workflows/scan.yml b/.github/workflows/scan.yml index 77b66ba8bf1..59ef1e0734e 100644 --- a/.github/workflows/scan.yml +++ b/.github/workflows/scan.yml @@ -7,8 +7,14 @@ on: - "main" - "rc" - "hotfix-rc" + pull_request: + types: [opened, synchronize, reopened] + branches-ignore: + - main pull_request_target: - types: [opened, synchronize] + types: [opened, synchronize, reopened] + branches: + - "main" jobs: check-run: @@ -68,7 +74,7 @@ jobs: ref: ${{ github.event.pull_request.head.sha }} - name: Scan with SonarCloud - uses: sonarsource/sonarqube-scan-action@bfd4e558cda28cda6b5defafb9232d191be8c203 # v4.2.1 + uses: sonarsource/sonarqube-scan-action@2500896589ef8f7247069a56136f8dc177c27ccf # v5.2.0 env: SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} with: diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 6411337f6e9..a8bfd368884 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -8,7 +8,9 @@ on: - "rc" - "hotfix-rc-*" pull_request: - types: [opened, synchronize] + types: [ opened, synchronize ] + +permissions: {} jobs: @@ -58,7 +60,7 @@ jobs: run: npm test -- --coverage --maxWorkers=3 - name: Report test results - uses: dorny/test-reporter@31a54ee7ebcacc03a09ea97a7e5465a47b84aea5 # v1.9.1 + uses: dorny/test-reporter@6e6a65b7a0bd2c9197df7d0ae36ac5cee784230c # v2.0.0 if: ${{ github.event.pull_request.head.repo.full_name == github.repository && !cancelled() }} with: name: Test Results @@ -66,11 +68,14 @@ jobs: reporter: jest-junit fail-on-error: true - - name: Upload coverage to codecov.io - uses: codecov/codecov-action@1e68e06f1dbfde0e4cefc87efeba9e4643565303 # v5.1.2 - - name: Upload results to codecov.io - uses: codecov/test-results-action@4e79e65778be1cecd5df25e14af1eafb6df80ea9 # v1.0.2 + uses: codecov/test-results-action@f2dba722c67b86c6caa034178c6e4d35335f6706 # v1.1.0 + + - name: Upload test coverage + uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4.6.0 + with: + name: jest-coverage + path: ./coverage/lcov.info rust: name: Run Rust tests on ${{ matrix.os }} @@ -131,7 +136,7 @@ jobs: uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - name: Install rust - uses: dtolnay/rust-toolchain@c5a29ddb4d9d194e7c84ec8c3fba61b1c31fee8c # stable + uses: dtolnay/rust-toolchain@b3b07ba8b418998c39fb20f53e8b695cdcc8de1b # stable with: toolchain: stable components: llvm-tools @@ -148,7 +153,37 @@ jobs: working-directory: ./apps/desktop/desktop_native run: cargo llvm-cov --all-features --lcov --output-path lcov.info --workspace --no-cfg-coverage - - name: Upload to codecov.io - uses: codecov/codecov-action@13ce06bfc6bbe3ecf90edbbf1bc32fe5978ca1d3 # v5.3.1 + - name: Upload test coverage + uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4.6.0 with: - files: ./apps/desktop/desktop_native/lcov.info + name: rust-coverage + path: ./apps/desktop/desktop_native/lcov.info + + upload-codecov: + name: Upload to Codecov + runs-on: ubuntu-22.04 + needs: + - testing + - rust-coverage + steps: + - name: Check out repo + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + + - name: Download jest coverage + uses: actions/download-artifact@fa0a91b85d4f404e444e00e005971372dc801d16 # v4.1.8 + with: + name: jest-coverage + path: ./ + + - name: Download rust coverage + uses: actions/download-artifact@fa0a91b85d4f404e444e00e005971372dc801d16 # v4.1.8 + with: + name: rust-coverage + path: ./apps/desktop/desktop_native + + - name: Upload coverage to codecov.io + uses: codecov/codecov-action@ad3126e916f78f00edff4ed0317cf185271ccc2d # v5.4.2 + with: + files: | + ./lcov.info + ./apps/desktop/desktop_native/lcov.info diff --git a/.github/workflows/version-auto-bump.yml b/.github/workflows/version-auto-bump.yml index 9431eab37c5..e8bd1dde246 100644 --- a/.github/workflows/version-auto-bump.yml +++ b/.github/workflows/version-auto-bump.yml @@ -11,7 +11,7 @@ jobs: runs-on: ubuntu-24.04 steps: - name: Generate GH App token - uses: actions/create-github-app-token@d72941d797fd3113feb6b93fd0dec494b13a2547 # v1.12.0 + uses: actions/create-github-app-token@30bf6253fa41bdc8d1501d202ad15287582246b4 # v2.0.3 id: app-token with: app-id: ${{ secrets.BW_GHAPP_ID }} diff --git a/.gitignore b/.gitignore index e865fa6a8fb..0fa968aa47c 100644 --- a/.gitignore +++ b/.gitignore @@ -34,6 +34,9 @@ build # Testing coverage junit.xml +## The "base" root level folder is expected for some local tests that do +## comparisons between the current branch and a base branch (usually main) +base/ # Misc *.crx diff --git a/.storybook/format-args-for-code-snippet.ts b/.storybook/format-args-for-code-snippet.ts new file mode 100644 index 00000000000..bf36c153c0a --- /dev/null +++ b/.storybook/format-args-for-code-snippet.ts @@ -0,0 +1,33 @@ +import { argsToTemplate, StoryObj } from "@storybook/angular"; + +type RenderArgType = StoryObj["args"]; + +export const formatArgsForCodeSnippet = >( + args: RenderArgType, +) => { + const nonNullArgs = Object.entries(args as ComponentType).filter( + ([_, value]) => value !== null && value !== undefined, + ); + const functionArgs = nonNullArgs.filter(([_, value]) => typeof value === "function"); + const argsToFormat = nonNullArgs.filter(([_, value]) => typeof value !== "function"); + + const argsToTemplateIncludeKeys = [...functionArgs].map( + ([key, _]) => key as keyof RenderArgType, + ); + + const formattedNonFunctionArgs = argsToFormat + .map(([key, value]) => { + if (typeof value === "boolean") { + return `[${key}]="${value}"`; + } + + if (Array.isArray(value)) { + const formattedArray = value.map((v) => `'${v}'`).join(", "); + return `[${key}]="[${formattedArray}]"`; + } + return `${key}="${value}"`; + }) + .join(" "); + + return `${formattedNonFunctionArgs} ${argsToTemplate(args as ComponentType, { include: argsToTemplateIncludeKeys })}`; +}; diff --git a/.storybook/main.ts b/.storybook/main.ts index 9583d1fc6f2..879e87fe376 100644 --- a/.storybook/main.ts +++ b/.storybook/main.ts @@ -8,6 +8,8 @@ const config: StorybookConfig = { stories: [ "../libs/auth/src/**/*.mdx", "../libs/auth/src/**/*.stories.@(js|jsx|ts|tsx)", + "../libs/dirt/card/src/**/*.mdx", + "../libs/dirt/card/src/**/*.stories.@(js|jsx|ts|tsx)", "../libs/tools/send/send-ui/src/**/*.mdx", "../libs/tools/send/send-ui/src/**/*.stories.@(js|jsx|ts|tsx)", "../libs/vault/src/**/*.mdx", @@ -20,8 +22,7 @@ const config: StorybookConfig = { "../apps/browser/src/**/*.stories.@(js|jsx|ts|tsx)", "../bitwarden_license/bit-web/src/**/*.mdx", "../bitwarden_license/bit-web/src/**/*.stories.@(js|jsx|ts|tsx)", - "../libs/tools/card/src/**/*.mdx", - "../libs/tools/card/src/**/*.stories.@(js|jsx|ts|tsx)", + "../libs/angular/src/**/*.stories.@(js|jsx|ts|tsx)", ], addons: [ getAbsolutePath("@storybook/addon-links"), diff --git a/.storybook/preview.tsx b/.storybook/preview.tsx index 6bd28cfe809..59b5287f3a3 100644 --- a/.storybook/preview.tsx +++ b/.storybook/preview.tsx @@ -26,6 +26,9 @@ const preview: Preview = { wrapperDecorator, ], parameters: { + a11y: { + element: "#storybook-root", + }, controls: { matchers: { color: /(background|color)$/i, @@ -38,7 +41,12 @@ const preview: Preview = { order: ["Documentation", ["Introduction", "Colors", "Icons"], "Component Library"], }, }, - docs: { source: { type: "dynamic", excludeDecorators: true } }, + docs: { + source: { + type: "dynamic", + excludeDecorators: true, + }, + }, backgrounds: { disable: true, }, diff --git a/.storybook/test-runner.ts b/.storybook/test-runner.ts new file mode 100644 index 00000000000..208adf96214 --- /dev/null +++ b/.storybook/test-runner.ts @@ -0,0 +1,47 @@ +import { type TestRunnerConfig } from "@storybook/test-runner"; +import { injectAxe, checkA11y } from "axe-playwright"; + +const testRunnerConfig: TestRunnerConfig = { + setup() {}, + + async preVisit(page, context) { + return await injectAxe(page); + }, + + async postVisit(page, context) { + await page.waitForSelector("#storybook-root"); + // https://github.com/abhinaba-ghosh/axe-playwright#parameters-on-checka11y-axerun + await checkA11y( + // Playwright page instance. + page, + + // context + "#storybook-root", + + // axeOptions, see https://www.deque.com/axe/core-documentation/api-documentation/#parameters-axerun + { + detailedReport: true, + detailedReportOptions: { + // Includes the full html for invalid nodes + html: true, + }, + verbose: false, + }, + + // skipFailures + false, + + // reporter "v2" is terminal reporter, "html" writes results to file + "v2", + + // axeHtmlReporterOptions + // NOTE: set reporter param (above) to "html" to activate these options + { + outputDir: "reports/a11y", + reportFileName: `${context.id}.html`, + }, + ); + }, +}; + +export default testRunnerConfig; 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/angular.json b/angular.json index 665d810cf4e..87ee7dc57b2 100644 --- a/angular.json +++ b/angular.json @@ -6,6 +6,32 @@ "analytics": false }, "projects": { + "bit-web": { + "projectType": "application", + "schematics": { + "@schematics/angular:application": { + "strict": true + } + }, + "root": "bitwarden_license/bit-web", + "sourceRoot": "bitwarden_license/bit-web/src", + "prefix": "app", + "architect": { + "build": { + "builder": "@angular-devkit/build-angular:browser", + "options": { + "outputPath": "dist/web", + "index": "apps/web/src/index.html", + "main": "bitwarden_license/bit-web/src/app/main.ts", + "polyfills": "apps/web/src/polyfills.ts", + "tsConfig": "bitwarden_license/bit-web/tsconfig.json", + "assets": ["apps/web/src/favicon.ico"], + "styles": [], + "scripts": [] + } + } + } + }, "web": { "projectType": "application", "schematics": { @@ -22,8 +48,8 @@ "options": { "outputPath": "dist/web", "index": "apps/web/src/index.html", - "main": "apps/web/src/app/main.ts", - "polyfills": "apps/web/src/app/polyfills.ts", + "main": "apps/web/src/main.ts", + "polyfills": "apps/web/src/polyfills.ts", "tsConfig": "apps/web/tsconfig.json", "assets": ["apps/web/src/favicon.ico"], "styles": [], 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 @@ -[![Github Workflow build browser on master](https://github.com/bitwarden/clients/actions/workflows/build-browser.yml/badge.svg?branch=master)](https://github.com/bitwarden/clients/actions/workflows/build-browser.yml?query=branch:master) +[![Github Workflow build browser on main](https://github.com/bitwarden/clients/actions/workflows/build-browser.yml/badge.svg?branch=main)](https://github.com/bitwarden/clients/actions/workflows/build-browser.yml?query=branch:main) [![Crowdin](https://d322cqt584bo4o.cloudfront.net/bitwarden-browser/localized.svg)](https://crowdin.com/project/bitwarden-browser) [![Join the chat at https://gitter.im/bitwarden/Lobby](https://badges.gitter.im/bitwarden/Lobby.svg)](https://gitter.im/bitwarden/Lobby) @@ -15,7 +15,7 @@ The Bitwarden browser extension is written using the Web Extension API and Angular. -![My Vault](https://raw.githubusercontent.com/bitwarden/brand/master/screenshots/browser-chrome.png) +![My Vault](https://raw.githubusercontent.com/bitwarden/brand/main/screenshots/web-browser-extension-generator.png) ## Documentation diff --git a/apps/browser/jest.config.js b/apps/browser/jest.config.js index 73f5ada287a..14cd959810e 100644 --- a/apps/browser/jest.config.js +++ b/apps/browser/jest.config.js @@ -1,18 +1,17 @@ const { pathsToModuleNameMapper } = require("ts-jest"); -const { compilerOptions } = require("./tsconfig"); +const { compilerOptions } = require("../../tsconfig.base"); const sharedConfig = require("../../libs/shared/jest.config.angular"); /** @type {import('jest').Config} */ module.exports = { ...sharedConfig, - preset: "jest-preset-angular", setupFilesAfterEnv: ["/test.setup.ts"], moduleNameMapper: pathsToModuleNameMapper( - { "@bitwarden/common/spec": ["../../libs/common/spec"], ...(compilerOptions?.paths ?? {}) }, + { "@bitwarden/common/spec": ["libs/common/spec"], ...(compilerOptions?.paths ?? {}) }, { - prefix: "/", + prefix: "/../../", }, ), }; diff --git a/apps/browser/package.json b/apps/browser/package.json index b311b837e78..c44743add7c 100644 --- a/apps/browser/package.json +++ b/apps/browser/package.json @@ -1,6 +1,6 @@ { "name": "@bitwarden/browser", - "version": "2025.3.2", + "version": "2025.5.1", "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..ad4ca0d4c42 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,86 @@ "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.", + "notificationNewItemAria": { + "message": "New Item, opens in new window", + "description": "Aria label for the new item button in notification bar confirmation message when error is prompted" + }, + "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": { - "content": "$1" + "label": { + "content": "$1", + "example": "Login" } - }, - "description": "Shown to user after login is updated." + } + }, + "notificationLoginSaveConfirmation": { + "message": "saved to Bitwarden.", + "description": "Shown to user after item is saved." + }, + "notificationLoginUpdatedConfirmation": { + "message": "updated in Bitwarden.", + "description": "Shown to user after item is updated." + }, + "selectItemAriaLabel": { + "message": "Select $ITEMTYPE$, $ITEMNAME$", + "description": "Used by screen readers. $1 is the item type (like vault or folder), $2 is the selected item name.", + "placeholders": { + "itemType": { + "content": "$1" + }, + "itemName": { + "content": "$2" + } + } }, "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?", + "unlockToSave": { + "message": "Unlock to save this login", + "description": "User prompt to take action in order to save the login they just entered." + }, + "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 +1150,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 +1162,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 +1255,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 +1296,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 +1354,7 @@ "message": "الملف" }, "fileToShare": { - "message": "File to share" + "message": "ملف للمشاركة" }, "selectFile": { "message": "حدد ملفًا" @@ -1323,8 +1365,8 @@ "featureUnavailable": { "message": "الميزة غير متوفرة" }, - "encryptionKeyMigrationRequired": { - "message": "مطلوب نقل مفتاح التشفير. الرجاء تسجيل الدخول بواسطة مخزن الويب لتحديث مفتاح التشفير الخاص بك." + "legacyEncryptionUnsupported": { + "message": "Legacy encryption is no longer supported. Please contact support to recover your account." }, "premiumMembership": { "message": "العضوية المميزة" @@ -1348,7 +1390,7 @@ "message": "1 جيغابايت وحدة تخزين مشفرة لمرفقات الملفات." }, "premiumSignUpEmergency": { - "message": "Emergency access." + "message": "الوصول الطارئ." }, "premiumSignUpTwoStepOptions": { "message": "خيارات تسجيل الدخول بخطوتين المملوكة لجهات اخرى مثل YubiKey و Duo." @@ -1369,7 +1411,7 @@ "message": "شراء العضوية المميزة" }, "premiumPurchaseAlertV2": { - "message": "You can purchase Premium from your account settings on the Bitwarden web app." + "message": "يمكنك شراء العضوية المميزة من إعدادات حسابك على تطبيق بيتواردن على شبكة الإنترنت." }, "premiumCurrentMember": { "message": "أنت عضو مميز!" @@ -1378,7 +1420,7 @@ "message": "شكرا لك على دعم Bitwarden." }, "premiumFeatures": { - "message": "Upgrade to Premium and receive:" + "message": "الترقية إلى النسخة الممتازة و استلام:" }, "premiumPrice": { "message": "الكل فقط بـ $PRICE$ /سنة!", @@ -1390,7 +1432,7 @@ } }, "premiumPriceV2": { - "message": "All for just $PRICE$ per year!", + "message": "كل شيء فقط $PRICE$ في السنة!", "placeholders": { "price": { "content": "$1", @@ -1417,10 +1459,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 +1474,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 +1511,7 @@ "message": "خيارات تسجيل الدخول بخطوتين" }, "selectTwoStepLoginMethod": { - "message": "Select two-step login method" + "message": "حدد طريقة تسجيل الدخول بخطوتين" }, "recoveryCodeDesc": { "message": "هل تفقد الوصول إلى جميع مزودي التحقق بعاملين؟ استخدم رمز الاسترداد الخاص بك لتعطيل جميع مزودي التحقق بعاملين من حسابك." @@ -1481,17 +1523,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 +1550,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 +1571,7 @@ "message": "رابط الخادم" }, "selfHostBaseUrl": { - "message": "Self-host server URL", + "message": "رابط خادم الاستضافة الذاتية", "description": "Label for field requesting a self-hosted integration service URL" }, "apiUrl": { @@ -1555,22 +1597,40 @@ "description": "Represents the message for allowing the user to enable the autofill overlay" }, "autofillSuggestionsSectionTitle": { - "message": "Autofill suggestions" + "message": "اقتراحات التعبئة التلقائية" + }, + "autofillSpotlightTitle": { + "message": "Easily find autofill suggestions" + }, + "autofillSpotlightDesc": { + "message": "Turn off your browser's autofill settings, so they don't conflict with Bitwarden." + }, + "turnOffBrowserAutofill": { + "message": "Turn off $BROWSER$ autofill", + "placeholders": { + "browser": { + "content": "$1", + "example": "Chrome" + } + } + }, + "turnOffAutofill": { + "message": "Turn off autofill" }, "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 +1651,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 +1663,7 @@ "message": "مواقع المساومة أو غير الموثوق بها يمكن أن تستغل الملء التلقائي في تحميل الصفحة." }, "learnMoreAboutAutofillOnPageLoadLinkText": { - "message": "Learn more about risks" + "message": "معرفة المزيد عن المخاطر" }, "learnMoreAboutAutofill": { "message": "تعرف على المزيد حول الملء التلقائي" @@ -1633,13 +1693,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 +1723,7 @@ "message": "اسحب للفرز" }, "dragToReorder": { - "message": "Drag to reorder" + "message": "اسحب لإعادة الترتيب" }, "cfTypeText": { "message": "نص" @@ -1675,7 +1735,7 @@ "message": "قيمة منطقية" }, "cfTypeCheckbox": { - "message": "Checkbox" + "message": "خانة" }, "cfTypeLinked": { "message": "مرتبط", @@ -1860,10 +1920,10 @@ "message": "الهوية" }, "typeSshKey": { - "message": "SSH key" + "message": "مفتاح SSH" }, "newItemHeader": { - "message": "New $TYPE$", + "message": "جديد $TYPE$", "placeholders": { "type": { "content": "$1", @@ -1872,7 +1932,7 @@ } }, "editItemHeader": { - "message": "Edit $TYPE$", + "message": "تحرير $TYPE$", "placeholders": { "type": { "content": "$1", @@ -1881,7 +1941,7 @@ } }, "viewItemHeader": { - "message": "View $TYPE$", + "message": "عرض $TYPE$", "placeholders": { "type": { "content": "$1", @@ -1893,13 +1953,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 +1968,7 @@ "message": "المجموعات" }, "nCollections": { - "message": "$COUNT$ collections", + "message": "مجموعات $COUNT$", "placeholders": { "count": { "content": "$1", @@ -1938,7 +1998,7 @@ "message": "ملاحظات آمنة" }, "sshKeys": { - "message": "SSH Keys" + "message": "مفاتيح SSH" }, "clear": { "message": "مسح", @@ -1964,7 +2024,7 @@ "description": "Domain name. Ex. website.com" }, "baseDomainOptionRecommended": { - "message": "Base domain (recommended)", + "message": "النطاق الأساسي (مستحسن)", "description": "Domain name. Ex. website.com" }, "domainName": { @@ -2018,7 +2078,7 @@ "message": "لا توجد كلمات مرور للعرض." }, "clearHistory": { - "message": "Clear history" + "message": "مسح المحفوظات" }, "nothingToShow": { "message": "لا يوجد شيء لعرضه" @@ -2139,11 +2199,14 @@ "message": "مولد اسم المستخدم" }, "useThisEmail": { - "message": "Use this email" + "message": "استخدم هذا البريد الإلكتروني" }, "useThisPassword": { "message": "استخدم كلمة المرور هذه" }, + "useThisPassphrase": { + "message": "Use this passphrase" + }, "useThisUsername": { "message": "استخدم اسم المستخدم هذا" }, @@ -2159,22 +2222,13 @@ "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" - }, - "newCustomizationOptionsCalloutTitle": { - "message": "New customization options" - }, - "newCustomizationOptionsCalloutContent": { - "message": "Customize your vault experience with quick copy actions, compact mode, and more!" - }, - "newCustomizationOptionsCalloutLink": { - "message": "View all Appearance settings" + "message": "إجراء المهلة" }, "lock": { "message": "قفل", @@ -2323,7 +2377,7 @@ "message": "سياسة الخصوصية" }, "yourNewPasswordCannotBeTheSameAsYourCurrentPassword": { - "message": "Your new password cannot be the same as your current password." + "message": "كلمة المرور الجديدة لا يمكن أن تكون نفس كلمة المرور الحالية." }, "hintEqualsPassword": { "message": "لا يمكن أن يكون تلميح كلمة المرور نفس كلمة المرور الخاصة بك." @@ -2332,10 +2386,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 +2431,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 +2446,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 +2479,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 +2504,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", @@ -2623,6 +2677,10 @@ "message": "كل الإرسالات", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, + "maxAccessCountReached": { + "message": "Max access count reached", + "description": "This text will be displayed after a Send has been accessed the maximum amount of times." + }, "hideTextByDefault": { "message": "إخفاء النص بشكل افتراضي" }, @@ -2715,27 +2773,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 +2803,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 +2817,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 +2854,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 +2878,13 @@ "message": "تأكيد البريد الإلكتروني مطلوب" }, "emailVerifiedV2": { - "message": "Email verified" + "message": "تم التحقق من البريد الإلكتروني" }, "emailVerificationRequiredDesc": { "message": "يجب عليك تأكيد بريدك الإلكتروني لاستخدام هذه الميزة. يمكنك تأكيد بريدك الإلكتروني في خزانة الويب." }, "updatedMasterPassword": { - "message": "Updated master password" + "message": "تحديث كلمة المرور الرئيسية" }, "updateMasterPassword": { "message": "تحديث كلمة المرور الرئيسية" @@ -2835,10 +2893,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 +2905,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 +2939,7 @@ "message": "دقائق" }, "vaultTimeoutPolicyAffectingOptions": { - "message": "Enterprise policy requirements have been applied to your timeout options" + "message": "تم تطبيق متطلبات سياسة المؤسسة على خيارات المهلة الخاصة بك" }, "vaultTimeoutPolicyInEffect": { "message": "سياسات مؤسستك تؤثر على مهلة الخزانة الخاص بك. الحد الأقصى المسموح به لمهلة الخزانة هو $HOURS$ ساعة/ساعات و $MINUTES$ دقيقة/دقائق.", @@ -2897,7 +2955,7 @@ } }, "vaultTimeoutPolicyInEffect1": { - "message": "$HOURS$ hour(s) and $MINUTES$ minute(s) maximum.", + "message": "$HOURS$ ساعة(ساعات) و $MINUTES$ دقيقة(دقائق) كحد أقصى.", "placeholders": { "hours": { "content": "$1", @@ -2910,7 +2968,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 +2981,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 +2998,7 @@ } }, "vaultTimeoutActionPolicyInEffect": { - "message": "Your organization policies have set your vault timeout action to $ACTION$.", + "message": "لقد حددت سياسات مؤسستك إجراء مهلة المخزن الخاص بك إلى $ACTION$.", "placeholders": { "action": { "content": "$1", @@ -2963,14 +3021,14 @@ "copyCustomFieldNameNotUnique": { "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.", - "placeholders": { - "organization": { - "content": "$1", - "example": "My Org Name" - } - } + "removeMasterPasswordForOrganizationUserKeyConnector": { + "message": "A master password is no longer required for members of the following organization. Please confirm the domain below with your organization administrator." + }, + "organizationName": { + "message": "Organization name" + }, + "keyConnectorDomain": { + "message": "Key Connector domain" }, "leaveOrganization": { "message": "مغادرة المؤسسة" @@ -2988,7 +3046,7 @@ "message": "لقد غادرت المؤسسة." }, "toggleCharacterCount": { - "message": "Toggle character count" + "message": "تبديل عدد الأحرف" }, "sessionTimeout": { "message": "انتهت مدة جلستك. يرجى العودة ومحاولة تسجيل الدخول مرة أخرى." @@ -2997,7 +3055,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 +3064,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 +3073,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 +3088,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 +3122,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 +3132,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 +3142,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": { @@ -3532,12 +3590,36 @@ "deviceTrusted": { "message": "Device trusted" }, - "sendsNoItemsTitle": { - "message": "No active Sends", + "trustOrganization": { + "message": "Trust organization" + }, + "trust": { + "message": "Trust" + }, + "doNotTrust": { + "message": "Do not trust" + }, + "organizationNotTrusted": { + "message": "Organization is not trusted" + }, + "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" + }, + "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." + }, + "orgTrustWarning1": { + "message": "This organization has an Enterprise policy that will enroll you in account recovery. Enrollment will allow organization administrators to change your password. Only proceed if you recognize this organization and the fingerprint phrase displayed below matches the organization's fingerprint." + }, + "trustUser": { + "message": "Trust user" + }, + "sendsTitleNoItems": { + "message": "Send sensitive information safely", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, - "sendsNoItemsMessage": { - "message": "Use Send to securely share encrypted information with anyone.", + "sendsBodyNoItems": { + "message": "Share files and data securely with anyone, on any platform. Your information will remain end-to-end encrypted while limiting exposure.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "inputRequired": { @@ -4456,6 +4538,33 @@ } } }, + "downloadBitwarden": { + "message": "Download Bitwarden" + }, + "downloadBitwardenOnAllDevices": { + "message": "Download Bitwarden on all devices" + }, + "getTheMobileApp": { + "message": "Get the mobile app" + }, + "getTheMobileAppDesc": { + "message": "Access your passwords on the go with the Bitwarden mobile app." + }, + "getTheDesktopApp": { + "message": "Get the desktop app" + }, + "getTheDesktopAppDesc": { + "message": "Access your vault without a browser, then set up unlock with biometrics to expedite unlocking in both the desktop app and browser extension." + }, + "downloadFromBitwardenNow": { + "message": "Download from bitwarden.com now" + }, + "getItOnGooglePlay": { + "message": "Get it on Google Play" + }, + "downloadOnTheAppStore": { + "message": "Download on the App Store" + }, "permanentlyDeleteAttachmentConfirmation": { "message": "Are you sure you want to permanently delete this attachment?" }, @@ -4917,6 +5026,15 @@ "biometricsStatusHelptextUnavailableReasonUnknown": { "message": "Biometric unlock is currently unavailable for an unknown reason." }, + "unlockVault": { + "message": "Unlock your vault in seconds" + }, + "unlockVaultDesc": { + "message": "You can customize your unlock and timeout settings to more quickly access your vault." + }, + "unlockPinSet": { + "message": "Unlock PIN set" + }, "authenticating": { "message": "Authenticating" }, @@ -4928,8 +5046,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": { @@ -5079,42 +5197,6 @@ "beta": { "message": "Beta" }, - "importantNotice": { - "message": "Important notice" - }, - "setupTwoStepLogin": { - "message": "Set up two-step login" - }, - "newDeviceVerificationNoticeContentPage1": { - "message": "Bitwarden will send a code to your account email to verify logins from new devices starting in February 2025." - }, - "newDeviceVerificationNoticeContentPage2": { - "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" - }, - "newDeviceVerificationNoticePageOneFormContent": { - "message": "Do you have reliable access to your email, $EMAIL$?", - "placeholders": { - "email": { - "content": "$1", - "example": "your_name@email.com" - } - } - }, - "newDeviceVerificationNoticePageOneEmailAccessNo": { - "message": "No, I do not" - }, - "newDeviceVerificationNoticePageOneEmailAccessYes": { - "message": "Yes, I can reliably access my email" - }, - "turnOnTwoStepLogin": { - "message": "Turn on two-step login" - }, - "changeAcctEmail": { - "message": "Change account email" - }, "extensionWidth": { "message": "Extension width" }, @@ -5169,6 +5251,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 +5283,98 @@ }, "secureDevicesBody": { "message": "Save unlimited passwords across unlimited devices with Bitwarden mobile, browser, and desktop apps." + }, + "nudgeBadgeAria": { + "message": "1 notification" + }, + "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!" + }, + "hasItemsVaultNudgeBodyOne": { + "message": "Autofill items for the current page" + }, + "hasItemsVaultNudgeBodyTwo": { + "message": "Favorite items for easy access" + }, + "hasItemsVaultNudgeBodyThree": { + "message": "Search your vault for something else" + }, + "newLoginNudgeTitle": { + "message": "Save time with autofill" + }, + "newLoginNudgeBodyOne": { + "message": "Include a", + "description": "This is in multiple parts to allow for bold text in the middle of the sentence.", + "example": "Include a Website so this login appears as an autofill suggestion." + }, + "newLoginNudgeBodyBold": { + "message": "Website", + "description": "This is in multiple parts to allow for bold text in the middle of the sentence.", + "example": "Include a Website so this login appears as an autofill suggestion." + }, + "newLoginNudgeBodyTwo": { + "message": "so this login appears as an autofill suggestion.", + "description": "This is in multiple parts to allow for bold text in the middle of the sentence.", + "example": "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" + }, + "newSshNudgeBodyOne": { + "message": "Store your keys and connect with the SSH agent for fast, encrypted authentication.", + "description": "Two part message", + "example": "Store your keys and connect with the SSH agent for fast, encrypted authentication. Learn more about SSH agent" + }, + "newSshNudgeBodyTwo": { + "message": "Learn more about SSH agent", + "description": "Two part message", + "example": "Store your keys and connect with the SSH agent for fast, encrypted authentication. Learn more about SSH agent" + }, + "generatorNudgeTitle": { + "message": "Quickly create passwords" + }, + "generatorNudgeBodyOne": { + "message": "Easily create strong and unique passwords by clicking on", + "description": "Two part message", + "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." + }, + "generatorNudgeBodyTwo": { + "message": "to help you keep your logins secure.", + "description": "Two part message", + "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." + }, + "generatorNudgeBodyAria": { + "message": "Easily create strong and unique passwords by clicking on the Generate password button to help you keep your logins secure.", + "description": "Aria label for the body content of the generator nudge" + }, + "noPermissionsViewPage": { + "message": "You do not have permissions to view this page. Try logging in with a different account." } } diff --git a/apps/browser/src/_locales/az/messages.json b/apps/browser/src/_locales/az/messages.json index 41e6d9b5ab9..2bf1226047d 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,55 @@ "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.", + "notificationNewItemAria": { + "message": "Yeni element, yeni bir pəncərədə açılır", + "description": "Aria label for the new item button in notification bar confirmation message when error is prompted" + }, + "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": { - "content": "$1" + "label": { + "content": "$1", + "example": "Login" } - }, - "description": "Shown to user after login is updated." + } + }, + "notificationLoginSaveConfirmation": { + "message": "\"Bitwarden\"də saxlanıldı.", + "description": "Shown to user after item is saved." + }, + "notificationLoginUpdatedConfirmation": { + "message": "\"Bitwarden\"də güncəlləndi.", + "description": "Shown to user after item is updated." + }, + "selectItemAriaLabel": { + "message": "$ITEMTYPE$, $ITEMNAME$ seç", + "description": "Used by screen readers. $1 is the item type (like vault or folder), $2 is the selected item name.", + "placeholders": { + "itemType": { + "content": "$1" + }, + "itemName": { + "content": "$2" + } + } }, "saveAsNewLoginAction": { "message": "Yeni giriş kimi saxla", @@ -1082,12 +1120,16 @@ "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?", + "unlockToSave": { + "message": "Bu girişi saxlamaq üçün kilidi açın", + "description": "User prompt to take action in order to save the login they just entered." + }, + "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 +1141,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 +1150,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 +1162,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": { @@ -1323,8 +1365,8 @@ "featureUnavailable": { "message": "Özəllik əlçatmazdır" }, - "encryptionKeyMigrationRequired": { - "message": "Şifrələmə açarının daşınması tələb olunur. Şifrələmə açarınızı güncəlləmək üçün lütfən veb seyfinizə giriş edin." + "legacyEncryptionUnsupported": { + "message": "Köhnə şifrələmə artıq dəstəklənmir. Hesabınızı geri qaytarmaq üçün lütfən dəstəklə əlaqə saxlayın." }, "premiumMembership": { "message": "Premium üzvlük" @@ -1557,6 +1599,24 @@ "autofillSuggestionsSectionTitle": { "message": "Avto-doldurma təklifləri" }, + "autofillSpotlightTitle": { + "message": "Avto-doldurma təkliflərini asanlıqla tap" + }, + "autofillSpotlightDesc": { + "message": "Bitwarden ilə ziddiyyət yaranmaması üçün brauzerinizin avto-doldurma ayarlarını söndürün." + }, + "turnOffBrowserAutofill": { + "message": "$BROWSER$ avto-doldurma ayarını söndür", + "placeholders": { + "browser": { + "content": "$1", + "example": "Chrome" + } + } + }, + "turnOffAutofill": { + "message": "Avto-doldurmanı söndür" + }, "showInlineMenuLabel": { "message": "Avto-doldurma təkliflərini form xanalarında göstər" }, @@ -2144,6 +2204,9 @@ "useThisPassword": { "message": "Bu parolu istifadə et" }, + "useThisPassphrase": { + "message": "Bu keçid ifadəsini istifadə et" + }, "useThisUsername": { "message": "Bu istifadəçi adını istifadə et" }, @@ -2167,15 +2230,6 @@ "vaultTimeoutAction1": { "message": "Vaxt bitmə əməliyyatı" }, - "newCustomizationOptionsCalloutTitle": { - "message": "Yeni özəlləşdirmə seçimləri" - }, - "newCustomizationOptionsCalloutContent": { - "message": "Cəld kopyalama fəaliyyəti, yığcam rejim və daha çoxu ilə seyf təcrübənizi özəlləşdirin!" - }, - "newCustomizationOptionsCalloutLink": { - "message": "Bütün Görünüş ayarlarına bax" - }, "lock": { "message": "Kilidlə", "description": "Verb form: to make secure or inaccessible by" @@ -2623,6 +2677,10 @@ "message": "Bütün \"Send\"lər", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, + "maxAccessCountReached": { + "message": "Maksimal müraciət sayına çatıldı", + "description": "This text will be displayed after a Send has been accessed the maximum amount of times." + }, "hideTextByDefault": { "message": "Mətni ilkin olaraq gizlət" }, @@ -2963,14 +3021,14 @@ "copyCustomFieldNameNotUnique": { "message": "Unikal identifikator tapılmadı." }, - "convertOrganizationEncryptionDesc": { - "message": "$ORGANIZATION$, self-hosted açar serveri ilə SSO istifadə edir. Bu təşkilatın üzvlərinin giriş etməsi üçün artıq ana parol tələb edilməyəcək.", - "placeholders": { - "organization": { - "content": "$1", - "example": "My Org Name" - } - } + "removeMasterPasswordForOrganizationUserKeyConnector": { + "message": "Aşağıdakı təşkilatların üzvləri üçün artıq ana parol tələb olunmur. Lütfən aşağıdakı domeni təşkilatınızın inzibatçısı ilə təsdiqləyin." + }, + "organizationName": { + "message": "Təşkilat adı" + }, + "keyConnectorDomain": { + "message": "Key Connector domeni" }, "leaveOrganization": { "message": "Təşkilatı tərk et" @@ -3259,7 +3317,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" @@ -3532,12 +3590,36 @@ "deviceTrusted": { "message": "Cihaz güvənlidir" }, - "sendsNoItemsTitle": { - "message": "Aktiv \"Send\" yoxdur", + "trustOrganization": { + "message": "Təşkilata güvən" + }, + "trust": { + "message": "Güvən" + }, + "doNotTrust": { + "message": "Güvənmə" + }, + "organizationNotTrusted": { + "message": "Təşkilata güvənilmir" + }, + "emergencyAccessTrustWarning": { + "message": "Hesabınızın təhlükəsizliyi üçün yalnız bu istifadəçiyə fövqəladə hal müraciəti icazəsini verdiyinizi və onun barmaq izinin hesabında görünən barmaq izi ilə uyuşduğunu təsdiqləyin" + }, + "orgTrustWarning": { + "message": "Hesabınızın təhlükəsizliyi üçün yalnız bu təşkilatın üzvüsünüzsə, hesab geri qaytarma fəaldırsa və aşağıda görünən barmaq izi təşkilatın barmaq izi ilə uyuşursa davam edin." + }, + "orgTrustWarning1": { + "message": "Bu təşkilat, sahib olduğu Müəssisə siyasəti ilə sizi hesabın qaytarılması prosesinə yazdıracaq. Yazılma, təşkilat inzibatçılarının parolunuzu dəyişdirməsinə imkan verəcək. Yalnız bu təşkilatı tanıyırsınızsa və aşağıda görünən barmaq izi ifadəsi, təşkilatın barmaq izi ifadəsi ilə uyuşursa davam edin." + }, + "trustUser": { + "message": "İstifadəçiyə güvən" + }, + "sendsTitleNoItems": { + "message": "Send, həssas məlumatlar təhlükəsizdir", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, - "sendsNoItemsMessage": { - "message": "Şifrələnmiş məlumatları hər kəslə güvənli şəkildə paylaşmaq üçün \"Send\"i istifadə edin.", + "sendsBodyNoItems": { + "message": "İstənilən platformada faylları və dataları hər kəslə paylaşın. İfşa olunmağı məhdudlaşdıraraq məlumatlarınız ucdan-uca şifrələnmiş qalacaq.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "inputRequired": { @@ -4456,6 +4538,33 @@ } } }, + "downloadBitwarden": { + "message": "Bitwarden-i endir" + }, + "downloadBitwardenOnAllDevices": { + "message": "Bitwarden-i bütün cihazlarda endir" + }, + "getTheMobileApp": { + "message": "Mobil tətbiqi əldə et" + }, + "getTheMobileAppDesc": { + "message": "Bitwarden mobil tətbiqi ilə parollarınıza hər yerdən müraciət edin." + }, + "getTheDesktopApp": { + "message": "Masaüstü tətbiqi əldə et" + }, + "getTheDesktopAppDesc": { + "message": "Seyfinizə brauzer olmadan müraciət edin, sonra həm masaüstü tətbiqində, həm də brauzer uzantısında kilid açma prosesini sürətləndirmək üçün biometrik ilə kilid açma prosesini qurun." + }, + "downloadFromBitwardenNow": { + "message": "İndi bitwarden.com saytından endir" + }, + "getItOnGooglePlay": { + "message": "Google Play-dən endir" + }, + "downloadOnTheAppStore": { + "message": "App Store-dan endir" + }, "permanentlyDeleteAttachmentConfirmation": { "message": "Bu qoşmanı birdəfəlik silmək istədiyinizə əminsiniz?" }, @@ -4917,6 +5026,15 @@ "biometricsStatusHelptextUnavailableReasonUnknown": { "message": "Bilinməyən bilinməyən bir səbəbə görə biometrik kilid açma əlçatmazdır." }, + "unlockVault": { + "message": "Seyfinizin kilidini saniyələr ərzində açın" + }, + "unlockVaultDesc": { + "message": "Seyfinizə daha cəld müraciət etmək üçün kilid açma və bitmə vaxtı ayarlarını özəlləşdirə bilərsiniz." + }, + "unlockPinSet": { + "message": "PIN ilə kilid açma təyini" + }, "authenticating": { "message": "Kimlik doğrulama" }, @@ -4928,8 +5046,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": { @@ -5079,42 +5197,6 @@ "beta": { "message": "Beta" }, - "importantNotice": { - "message": "Vacib bildiriş" - }, - "setupTwoStepLogin": { - "message": "İki addımlı girişi qur" - }, - "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." - }, - "newDeviceVerificationNoticeContentPage2": { - "message": "Hesabınızı qorumaq üçün alternativ bir yol kimi iki addımlı girişi qura və ya e-poçtunuzu müraciət edə biləcəyiniz e-poçtla dəyişdirə bilərsiniz." - }, - "remindMeLater": { - "message": "Daha sonra xatırlat" - }, - "newDeviceVerificationNoticePageOneFormContent": { - "message": "$EMAIL$ e-poçtunuza güvənli şəkildə müraciət edə bilirsiniz?", - "placeholders": { - "email": { - "content": "$1", - "example": "your_name@email.com" - } - } - }, - "newDeviceVerificationNoticePageOneEmailAccessNo": { - "message": "Xeyr, edə bilmirəm" - }, - "newDeviceVerificationNoticePageOneEmailAccessYes": { - "message": "Bəli, e-poçtuma güvənli şəkildə müraciət edə bilirəm" - }, - "turnOnTwoStepLogin": { - "message": "İki addımlı girişi işə sal" - }, - "changeAcctEmail": { - "message": "Hesabın e-poçtunu dəyişdir" - }, "extensionWidth": { "message": "Uzantı eni" }, @@ -5169,6 +5251,12 @@ "changeAtRiskPassword": { "message": "Riskli parolları dəyişdir" }, + "settingsVaultOptions": { + "message": "Seyf seçimləri" + }, + "emptyVaultDescription": { + "message": "Seyf, yalnız parollarızı yox, həm də daha çoxunu qoruyur. Giriş məlumatlarınızı, kimlikləri, kartları və notları burada güvənli şəkildə saxlayın." + }, "introCarouselLabel": { "message": "Bitwarden-ə xoş gəlmisiniz" }, @@ -5195,5 +5283,98 @@ }, "secureDevicesBody": { "message": "Bitwarden mobil, brauzer və masaüstü tətbiqləri ilə limitsiz cihaz arasında limitsiz parol saxlayın." + }, + "nudgeBadgeAria": { + "message": "1 bildiriş" + }, + "emptyVaultNudgeTitle": { + "message": "Mövcud parolları daxilə köçür" + }, + "emptyVaultNudgeBody": { + "message": "Giriş məlumatlarını əllə daxil etmədən daha tez daxilə köçürmək üçün \"Daxilə köçürücü\"nü istifadə edin." + }, + "emptyVaultNudgeButton": { + "message": "İndi daxilə köçür" + }, + "hasItemsVaultNudgeTitle": { + "message": "Seyfinizə xoş gəlmisiniz!" + }, + "hasItemsVaultNudgeBodyOne": { + "message": "Hazırkı səhifə üçün elementləri avto-doldur" + }, + "hasItemsVaultNudgeBodyTwo": { + "message": "Asan müraciət üçün elementləri sevimlilərə əlavə et" + }, + "hasItemsVaultNudgeBodyThree": { + "message": "Seyfinizdə başqa bir şey axtarın" + }, + "newLoginNudgeTitle": { + "message": "Avto-doldurma ilə vaxta qənaət edin" + }, + "newLoginNudgeBodyOne": { + "message": "Bir veb sayt", + "description": "This is in multiple parts to allow for bold text in the middle of the sentence.", + "example": "Include a Website so this login appears as an autofill suggestion." + }, + "newLoginNudgeBodyBold": { + "message": "daxil edin ki,", + "description": "This is in multiple parts to allow for bold text in the middle of the sentence.", + "example": "Include a Website so this login appears as an autofill suggestion." + }, + "newLoginNudgeBodyTwo": { + "message": "bu giriş məlumatları avto-doldurma təklifi kimi görünsün.", + "description": "This is in multiple parts to allow for bold text in the middle of the sentence.", + "example": "Include a Website so this login appears as an autofill suggestion." + }, + "newCardNudgeTitle": { + "message": "Problemsiz onlayn ödəniş" + }, + "newCardNudgeBody": { + "message": "Kartlarla, ödəniş xanalarını təhlükəsiz və doğru şəkildə avtomatik doldurun." + }, + "newIdentityNudgeTitle": { + "message": "Hesab yaratmanı sadələşdirin" + }, + "newIdentityNudgeBody": { + "message": "Kimliklərinizlə, uzun qeydiyyat və ya əlaqə xanalarını daha tez avtomatik doldurun." + }, + "newNoteNudgeTitle": { + "message": "Həssas datalarınızı güvənli şəkildə saxlayın" + }, + "newNoteNudgeBody": { + "message": "Notlarla, bankçılıq və ya sığorta təfsilatları kimi həssas dataları təhlükəsiz saxlayın." + }, + "newSshNudgeTitle": { + "message": "Gəlişdirici dostu SSH müraciəti" + }, + "newSshNudgeBodyOne": { + "message": "Açarlarınızı saxlayın və sürətli, şifrələnmiş kimlik doğrulama üçün SSH agentinə bağlayın.", + "description": "Two part message", + "example": "Store your keys and connect with the SSH agent for fast, encrypted authentication. Learn more about SSH agent" + }, + "newSshNudgeBodyTwo": { + "message": "SSH agenti barədə daha ətraflı", + "description": "Two part message", + "example": "Store your keys and connect with the SSH agent for fast, encrypted authentication. Learn more about SSH agent" + }, + "generatorNudgeTitle": { + "message": "Cəld parol yaradın" + }, + "generatorNudgeBodyOne": { + "message": "Klikləyərək güclü və unikal parolları asanlıqla yaradın", + "description": "Two part message", + "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." + }, + "generatorNudgeBodyTwo": { + "message": "və girişlərinizi güvənli şəkildə saxlayın.", + "description": "Two part message", + "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." + }, + "generatorNudgeBodyAria": { + "message": "Girişlərinizi güvənli şəkildə saxlamağınıza kömək etməsi üçün Parol yarat düyməsinə klikləyərək güclü və unikal parolları asanlıqla yaradın.", + "description": "Aria label for the body content of the generator nudge" + }, + "noPermissionsViewPage": { + "message": "Bu səhifəyə baxmaq icazəniz yoxdur. Fərqli hesabla giriş etməyə çalışın." } } diff --git a/apps/browser/src/_locales/be/messages.json b/apps/browser/src/_locales/be/messages.json index f828400c575..93314ce58de 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,55 @@ "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.", + "notificationNewItemAria": { + "message": "New Item, opens in new window", + "description": "Aria label for the new item button in notification bar confirmation message when error is prompted" + }, + "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": { - "content": "$1" + "label": { + "content": "$1", + "example": "Login" } - }, - "description": "Shown to user after login is updated." + } + }, + "notificationLoginSaveConfirmation": { + "message": "saved to Bitwarden.", + "description": "Shown to user after item is saved." + }, + "notificationLoginUpdatedConfirmation": { + "message": "updated in Bitwarden.", + "description": "Shown to user after item is updated." + }, + "selectItemAriaLabel": { + "message": "Select $ITEMTYPE$, $ITEMNAME$", + "description": "Used by screen readers. $1 is the item type (like vault or folder), $2 is the selected item name.", + "placeholders": { + "itemType": { + "content": "$1" + }, + "itemName": { + "content": "$2" + } + } }, "saveAsNewLoginAction": { "message": "Save as new login", @@ -1082,12 +1120,16 @@ "message": "Update login", "description": "Button text for updating an existing login entry." }, - "saveLoginPrompt": { - "message": "Save login?", + "unlockToSave": { + "message": "Unlock to save this login", + "description": "User prompt to take action in order to save the login they just entered." + }, + "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": { @@ -1323,8 +1365,8 @@ "featureUnavailable": { "message": "Функцыя недаступна" }, - "encryptionKeyMigrationRequired": { - "message": "Encryption key migration required. Please login through the web vault to update your encryption key." + "legacyEncryptionUnsupported": { + "message": "Legacy encryption is no longer supported. Please contact support to recover your account." }, "premiumMembership": { "message": "Прэміяльны статус" @@ -1557,6 +1599,24 @@ "autofillSuggestionsSectionTitle": { "message": "Прапановы аўтазапаўнення" }, + "autofillSpotlightTitle": { + "message": "Easily find autofill suggestions" + }, + "autofillSpotlightDesc": { + "message": "Turn off your browser's autofill settings, so they don't conflict with Bitwarden." + }, + "turnOffBrowserAutofill": { + "message": "Turn off $BROWSER$ autofill", + "placeholders": { + "browser": { + "content": "$1", + "example": "Chrome" + } + } + }, + "turnOffAutofill": { + "message": "Turn off autofill" + }, "showInlineMenuLabel": { "message": "Show autofill suggestions on form fields" }, @@ -2144,6 +2204,9 @@ "useThisPassword": { "message": "Выкарыстоўваць гэты пароль" }, + "useThisPassphrase": { + "message": "Use this passphrase" + }, "useThisUsername": { "message": "Выкарыстоўваць гэта імя карыстальніка" }, @@ -2167,15 +2230,6 @@ "vaultTimeoutAction1": { "message": "Timeout action" }, - "newCustomizationOptionsCalloutTitle": { - "message": "New customization options" - }, - "newCustomizationOptionsCalloutContent": { - "message": "Customize your vault experience with quick copy actions, compact mode, and more!" - }, - "newCustomizationOptionsCalloutLink": { - "message": "View all Appearance settings" - }, "lock": { "message": "Заблакіраваць", "description": "Verb form: to make secure or inaccessible by" @@ -2623,6 +2677,10 @@ "message": "Усе Send’ы", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, + "maxAccessCountReached": { + "message": "Max access count reached", + "description": "This text will be displayed after a Send has been accessed the maximum amount of times." + }, "hideTextByDefault": { "message": "Hide text by default" }, @@ -2963,14 +3021,14 @@ "copyCustomFieldNameNotUnique": { "message": "Не знойдзены ўнікальны ідэнтыфікатар." }, - "convertOrganizationEncryptionDesc": { - "message": "$ORGANIZATION$ выкарыстоўвае SSO з уласным серверам ключоў. Асноўны пароль для ўдзельнікаў гэтай арганізацыі больш не патрабуецца.", - "placeholders": { - "organization": { - "content": "$1", - "example": "My Org Name" - } - } + "removeMasterPasswordForOrganizationUserKeyConnector": { + "message": "A master password is no longer required for members of the following organization. Please confirm the domain below with your organization administrator." + }, + "organizationName": { + "message": "Organization name" + }, + "keyConnectorDomain": { + "message": "Key Connector domain" }, "leaveOrganization": { "message": "Выйсці з арганізацыі" @@ -3532,12 +3590,36 @@ "deviceTrusted": { "message": "Давераная прылада" }, - "sendsNoItemsTitle": { - "message": "Няма актыўных Send'аў", + "trustOrganization": { + "message": "Trust organization" + }, + "trust": { + "message": "Trust" + }, + "doNotTrust": { + "message": "Do not trust" + }, + "organizationNotTrusted": { + "message": "Organization is not trusted" + }, + "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" + }, + "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." + }, + "orgTrustWarning1": { + "message": "This organization has an Enterprise policy that will enroll you in account recovery. Enrollment will allow organization administrators to change your password. Only proceed if you recognize this organization and the fingerprint phrase displayed below matches the organization's fingerprint." + }, + "trustUser": { + "message": "Trust user" + }, + "sendsTitleNoItems": { + "message": "Send sensitive information safely", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, - "sendsNoItemsMessage": { - "message": "Выкарыстоўвайце Send'ы, каб бяспечна абагуляць зашыфраваную інфармацыю з іншымі.", + "sendsBodyNoItems": { + "message": "Share files and data securely with anyone, on any platform. Your information will remain end-to-end encrypted while limiting exposure.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "inputRequired": { @@ -4456,6 +4538,33 @@ } } }, + "downloadBitwarden": { + "message": "Download Bitwarden" + }, + "downloadBitwardenOnAllDevices": { + "message": "Download Bitwarden on all devices" + }, + "getTheMobileApp": { + "message": "Get the mobile app" + }, + "getTheMobileAppDesc": { + "message": "Access your passwords on the go with the Bitwarden mobile app." + }, + "getTheDesktopApp": { + "message": "Get the desktop app" + }, + "getTheDesktopAppDesc": { + "message": "Access your vault without a browser, then set up unlock with biometrics to expedite unlocking in both the desktop app and browser extension." + }, + "downloadFromBitwardenNow": { + "message": "Download from bitwarden.com now" + }, + "getItOnGooglePlay": { + "message": "Get it on Google Play" + }, + "downloadOnTheAppStore": { + "message": "Download on the App Store" + }, "permanentlyDeleteAttachmentConfirmation": { "message": "Are you sure you want to permanently delete this attachment?" }, @@ -4917,6 +5026,15 @@ "biometricsStatusHelptextUnavailableReasonUnknown": { "message": "Biometric unlock is currently unavailable for an unknown reason." }, + "unlockVault": { + "message": "Unlock your vault in seconds" + }, + "unlockVaultDesc": { + "message": "You can customize your unlock and timeout settings to more quickly access your vault." + }, + "unlockPinSet": { + "message": "Unlock PIN set" + }, "authenticating": { "message": "Authenticating" }, @@ -4928,8 +5046,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": { @@ -5079,42 +5197,6 @@ "beta": { "message": "Beta" }, - "importantNotice": { - "message": "Important notice" - }, - "setupTwoStepLogin": { - "message": "Set up two-step login" - }, - "newDeviceVerificationNoticeContentPage1": { - "message": "Bitwarden will send a code to your account email to verify logins from new devices starting in February 2025." - }, - "newDeviceVerificationNoticeContentPage2": { - "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" - }, - "newDeviceVerificationNoticePageOneFormContent": { - "message": "Do you have reliable access to your email, $EMAIL$?", - "placeholders": { - "email": { - "content": "$1", - "example": "your_name@email.com" - } - } - }, - "newDeviceVerificationNoticePageOneEmailAccessNo": { - "message": "No, I do not" - }, - "newDeviceVerificationNoticePageOneEmailAccessYes": { - "message": "Yes, I can reliably access my email" - }, - "turnOnTwoStepLogin": { - "message": "Turn on two-step login" - }, - "changeAcctEmail": { - "message": "Change account email" - }, "extensionWidth": { "message": "Extension width" }, @@ -5169,6 +5251,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 +5283,98 @@ }, "secureDevicesBody": { "message": "Save unlimited passwords across unlimited devices with Bitwarden mobile, browser, and desktop apps." + }, + "nudgeBadgeAria": { + "message": "1 notification" + }, + "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!" + }, + "hasItemsVaultNudgeBodyOne": { + "message": "Autofill items for the current page" + }, + "hasItemsVaultNudgeBodyTwo": { + "message": "Favorite items for easy access" + }, + "hasItemsVaultNudgeBodyThree": { + "message": "Search your vault for something else" + }, + "newLoginNudgeTitle": { + "message": "Save time with autofill" + }, + "newLoginNudgeBodyOne": { + "message": "Include a", + "description": "This is in multiple parts to allow for bold text in the middle of the sentence.", + "example": "Include a Website so this login appears as an autofill suggestion." + }, + "newLoginNudgeBodyBold": { + "message": "Website", + "description": "This is in multiple parts to allow for bold text in the middle of the sentence.", + "example": "Include a Website so this login appears as an autofill suggestion." + }, + "newLoginNudgeBodyTwo": { + "message": "so this login appears as an autofill suggestion.", + "description": "This is in multiple parts to allow for bold text in the middle of the sentence.", + "example": "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" + }, + "newSshNudgeBodyOne": { + "message": "Store your keys and connect with the SSH agent for fast, encrypted authentication.", + "description": "Two part message", + "example": "Store your keys and connect with the SSH agent for fast, encrypted authentication. Learn more about SSH agent" + }, + "newSshNudgeBodyTwo": { + "message": "Learn more about SSH agent", + "description": "Two part message", + "example": "Store your keys and connect with the SSH agent for fast, encrypted authentication. Learn more about SSH agent" + }, + "generatorNudgeTitle": { + "message": "Quickly create passwords" + }, + "generatorNudgeBodyOne": { + "message": "Easily create strong and unique passwords by clicking on", + "description": "Two part message", + "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." + }, + "generatorNudgeBodyTwo": { + "message": "to help you keep your logins secure.", + "description": "Two part message", + "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." + }, + "generatorNudgeBodyAria": { + "message": "Easily create strong and unique passwords by clicking on the Generate password button to help you keep your logins secure.", + "description": "Aria label for the body content of the generator nudge" + }, + "noPermissionsViewPage": { + "message": "You do not have permissions to view this page. Try logging in with a different account." } } diff --git a/apps/browser/src/_locales/bg/messages.json b/apps/browser/src/_locales/bg/messages.json index 457b0693a17..02e96f18bbc 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,55 @@ "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$.", + "notificationNewItemAria": { + "message": "Нов елемент, отваря се в нов прозорец", + "description": "Aria label for the new item button in notification bar confirmation message when error is prompted" + }, + "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": { - "content": "$1" + "label": { + "content": "$1", + "example": "Login" } - }, - "description": "Shown to user after login is updated." + } + }, + "notificationLoginSaveConfirmation": { + "message": "запазено в Битуорден.", + "description": "Shown to user after item is saved." + }, + "notificationLoginUpdatedConfirmation": { + "message": "обновено в Битуорден.", + "description": "Shown to user after item is updated." + }, + "selectItemAriaLabel": { + "message": "Избиране на $ITEMTYPE$, $ITEMNAME$", + "description": "Used by screen readers. $1 is the item type (like vault or folder), $2 is the selected item name.", + "placeholders": { + "itemType": { + "content": "$1" + }, + "itemName": { + "content": "$2" + } + } }, "saveAsNewLoginAction": { "message": "Запазване като нов елемент за вписване", @@ -1082,12 +1120,16 @@ "message": "Обновяване на данните за вписване", "description": "Button text for updating an existing login entry." }, - "saveLoginPrompt": { - "message": "Запазване на данните за вписване?", + "unlockToSave": { + "message": "Отключете, за да запазите тези данни за вписване", + "description": "User prompt to take action in order to save the login they just entered." + }, + "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": { @@ -1323,8 +1365,8 @@ "featureUnavailable": { "message": "Функцията е недостъпна" }, - "encryptionKeyMigrationRequired": { - "message": "Необходима е промяна на шифриращия ключ. Впишете се в трезора си по уеб, за да обновите своя шифриращ ключ." + "legacyEncryptionUnsupported": { + "message": "Остарелият метод на шифроване вече не се поддържа. Моля, свържете се с поддръжката, за да възстановите акаунта си." }, "premiumMembership": { "message": "Платен абонамент" @@ -1557,6 +1599,24 @@ "autofillSuggestionsSectionTitle": { "message": "Предложения за авт. попълване" }, + "autofillSpotlightTitle": { + "message": "Намирайте лесно предложения за авт. попълване" + }, + "autofillSpotlightDesc": { + "message": "Изключете настройките за автоматично попълване на браузъра си, за да не си пречат с Битуорден." + }, + "turnOffBrowserAutofill": { + "message": "Изключете автоматичното попълване на $BROWSER$", + "placeholders": { + "browser": { + "content": "$1", + "example": "Chrome" + } + } + }, + "turnOffAutofill": { + "message": "Изключване на автоматичното попълване" + }, "showInlineMenuLabel": { "message": "Показване на предложения за авт. попълване на полетата във формуляри" }, @@ -2144,6 +2204,9 @@ "useThisPassword": { "message": "Използване на тази парола" }, + "useThisPassphrase": { + "message": "Използване на тази парола-фраза" + }, "useThisUsername": { "message": "Използване на това потребителско име" }, @@ -2167,15 +2230,6 @@ "vaultTimeoutAction1": { "message": "Действие при изтичането на времето за достъп" }, - "newCustomizationOptionsCalloutTitle": { - "message": "Нови възможности за персонализиране" - }, - "newCustomizationOptionsCalloutContent": { - "message": "Персонализирайте трезора си с бързи действия за копиране, компактен режим и още!" - }, - "newCustomizationOptionsCalloutLink": { - "message": "Преглед на всички настройки за външния вид" - }, "lock": { "message": "Заключване", "description": "Verb form: to make secure or inaccessible by" @@ -2623,6 +2677,10 @@ "message": "Всички изпращания", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, + "maxAccessCountReached": { + "message": "Достигнат е максималният брой достъпвания", + "description": "This text will be displayed after a Send has been accessed the maximum amount of times." + }, "hideTextByDefault": { "message": "Скриване на текста по подразбиране" }, @@ -2963,14 +3021,14 @@ "copyCustomFieldNameNotUnique": { "message": "Няма намерен уникален идентификатор." }, - "convertOrganizationEncryptionDesc": { - "message": "$ORGANIZATION$ използва еднократно удостоверяване със собствен сървър за ключове. Членовете на тази организация вече нямат нужда от главна парола за вписване.", - "placeholders": { - "organization": { - "content": "$1", - "example": "My Org Name" - } - } + "removeMasterPasswordForOrganizationUserKeyConnector": { + "message": "За членовете на следната организация вече не се изисква главна парола. Потвърдете домейна по-долу с администратора на организацията си." + }, + "organizationName": { + "message": "Име на организацията" + }, + "keyConnectorDomain": { + "message": "Домейн на конектора за ключове" }, "leaveOrganization": { "message": "Напускане на организацията" @@ -3532,12 +3590,36 @@ "deviceTrusted": { "message": "Устройството е доверено" }, - "sendsNoItemsTitle": { - "message": "Няма активни Изпращания", + "trustOrganization": { + "message": "Даване на доверие на организацията" + }, + "trust": { + "message": "Даване на доверие" + }, + "doNotTrust": { + "message": "Да не се дава доверие" + }, + "organizationNotTrusted": { + "message": "Организацията не е доверена" + }, + "emergencyAccessTrustWarning": { + "message": "С оглед на сигурността на акаунта Ви, потвърдете само, ако сте дали на този потребител достъп за спешни случаи и ако отпечатъкът му съвпада с това, което се вижда в акаунта му" + }, + "orgTrustWarning": { + "message": "С оглед на сигурността на акаунта Ви, продължете само, ако сте член на тази организация, ако възстановяването на акаунта Ви е включено и ако отпечатъкът показан по-долу съвпада с този на организацията." + }, + "orgTrustWarning1": { + "message": "Тази организация има политика от плана за големи организации, която ще Ви включи в схемата за възстановяване на акаунти. Това включване ще позволи на администраторите да променят паролата Ви. Продължете само, ако разпознавате тази организация и уникалната фраза показана по-долу съвпада с отпечатъка на организацията." + }, + "trustUser": { + "message": "Даване на доверие на потребителя" + }, + "sendsTitleNoItems": { + "message": "Изпращайте чувствителна информация сигурно", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, - "sendsNoItemsMessage": { - "message": "Използвайте Изпращане, за да споделите безопасно шифрована информация с някого.", + "sendsBodyNoItems": { + "message": "Споделяйте сигурно файлове и данни с всекиго, през всяка система. Информацията Ви ще бъде защитена с шифроване от край до край, а видимостта ѝ ще бъде ограничена.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "inputRequired": { @@ -4456,6 +4538,33 @@ } } }, + "downloadBitwarden": { + "message": "Сваляне на Битуорден" + }, + "downloadBitwardenOnAllDevices": { + "message": "Сваляне на Битуорден на всички устройства" + }, + "getTheMobileApp": { + "message": "Свалете мобилното приложение" + }, + "getTheMobileAppDesc": { + "message": "Разполагайте с паролите си дори когато сте в движение, с мобилното приложение на Битуорден." + }, + "getTheDesktopApp": { + "message": "Свалете настолното приложение" + }, + "getTheDesktopAppDesc": { + "message": "Използвайте трезора си без браузър. Можете да настроите отключване с биометрични данни, за да ускорите отключването както в настолното приложение, така и в добавката за браузъра." + }, + "downloadFromBitwardenNow": { + "message": "Свалете от bitwarden.com сега" + }, + "getItOnGooglePlay": { + "message": "Вземете го от Google Play" + }, + "downloadOnTheAppStore": { + "message": "Свалете от App Store" + }, "permanentlyDeleteAttachmentConfirmation": { "message": "Наистина ли искате да изтриете завинаги този прикачен файл?" }, @@ -4917,6 +5026,15 @@ "biometricsStatusHelptextUnavailableReasonUnknown": { "message": "Отключването с биометрични данни не е налично по неизвестна причина." }, + "unlockVault": { + "message": "Отключвайте трезора си за секунди" + }, + "unlockVaultDesc": { + "message": "Можете да персонализирате настройките си за отключване и време на активност, за да получавате достъп до трезора си по-бързо." + }, + "unlockPinSet": { + "message": "Зададен е ПИН код за отключване" + }, "authenticating": { "message": "Удостоверяване" }, @@ -4928,8 +5046,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": { @@ -5079,42 +5197,6 @@ "beta": { "message": "Бета" }, - "importantNotice": { - "message": "Важно съобщение" - }, - "setupTwoStepLogin": { - "message": "Настройте двустепенно удостоверяване" - }, - "newDeviceVerificationNoticeContentPage1": { - "message": "Битуорден ще изпрати код до е-пощата Ви, за потвърждаване на вписването от нови устройства. Това ще започне от февруари 2025." - }, - "newDeviceVerificationNoticeContentPage2": { - "message": "Можете да настроите двустепенно удостоверяване, като различен метод на защита, или ако е необходимо да промените е-пощата си с такава, до която имате достъп." - }, - "remindMeLater": { - "message": "Напомнете ми по-късно" - }, - "newDeviceVerificationNoticePageOneFormContent": { - "message": "Имате ли сигурен достъп до е-пощата си – $EMAIL$?", - "placeholders": { - "email": { - "content": "$1", - "example": "your_name@email.com" - } - } - }, - "newDeviceVerificationNoticePageOneEmailAccessNo": { - "message": "Не, нямам" - }, - "newDeviceVerificationNoticePageOneEmailAccessYes": { - "message": "Да, имам достъп до е-пощата си" - }, - "turnOnTwoStepLogin": { - "message": "Включване на двустепенното удостоверяване" - }, - "changeAcctEmail": { - "message": "Промяна на е-пощата" - }, "extensionWidth": { "message": "Ширина на разширението" }, @@ -5169,6 +5251,12 @@ "changeAtRiskPassword": { "message": "Промяна на парола в риск" }, + "settingsVaultOptions": { + "message": "Настройки на трезора" + }, + "emptyVaultDescription": { + "message": "Трезорът може да пази не само паролите Ви. Съхранявайте защитени данни за вход, идентификационни данни, карти и бележки." + }, "introCarouselLabel": { "message": "Добре дошли в Битуорден" }, @@ -5195,5 +5283,98 @@ }, "secureDevicesBody": { "message": "Съхранявайте неограничен брой пароли на множество устройства – с приложенията на Битуорден за мобилни телефони, браузър и компютър." + }, + "nudgeBadgeAria": { + "message": "1 известие" + }, + "emptyVaultNudgeTitle": { + "message": "Внасяне на съществуващи пароли" + }, + "emptyVaultNudgeBody": { + "message": "Използвайте функцията за внасяне, за да прехвърлите лесно данните си за вписване в Битуорден, без да ги добавяте ръчно." + }, + "emptyVaultNudgeButton": { + "message": "Внасяне сега" + }, + "hasItemsVaultNudgeTitle": { + "message": "Добре дошли в трезора си!" + }, + "hasItemsVaultNudgeBodyOne": { + "message": "Попълвайте автоматично елементи в текущата страница" + }, + "hasItemsVaultNudgeBodyTwo": { + "message": "Добавете любими елементи за бърз достъп" + }, + "hasItemsVaultNudgeBodyThree": { + "message": "Потърсете нещо друго в трезора си" + }, + "newLoginNudgeTitle": { + "message": "Спестете време с автоматично попълване" + }, + "newLoginNudgeBodyOne": { + "message": "Добавете", + "description": "This is in multiple parts to allow for bold text in the middle of the sentence.", + "example": "Include a Website so this login appears as an autofill suggestion." + }, + "newLoginNudgeBodyBold": { + "message": "уеб сайт", + "description": "This is in multiple parts to allow for bold text in the middle of the sentence.", + "example": "Include a Website so this login appears as an autofill suggestion." + }, + "newLoginNudgeBodyTwo": { + "message": ", за да може тези данни за вписване да се появяват като предложение за автоматично попълване.", + "description": "This is in multiple parts to allow for bold text in the middle of the sentence.", + "example": "Include a Website so this login appears as an autofill suggestion." + }, + "newCardNudgeTitle": { + "message": "Безпроблемни плащания" + }, + "newCardNudgeBody": { + "message": "С възможността за запазване на карти, можете лесно да ги попълвате автоматично във формулярите – сигурно и точно." + }, + "newIdentityNudgeTitle": { + "message": "Опростено създаване на акаунти" + }, + "newIdentityNudgeBody": { + "message": "С възможността за запазване на самоличности, можете лесно да попълвате автоматично дълги формуляри за регистрация или връзка с уеб сайт." + }, + "newNoteNudgeTitle": { + "message": "Пазете тайните си на сигурно място" + }, + "newNoteNudgeBody": { + "message": "С възможността за запазване на бележки, можете да съхранявате тайна информация, като например банкови и застрахователни данни." + }, + "newSshNudgeTitle": { + "message": "Улеснен достъп за разработчици чрез SSH" + }, + "newSshNudgeBodyOne": { + "message": "Съхранявайте ключове и използвайте връзка чрез SSH агента, за бързо и шифровано удостоверяване.", + "description": "Two part message", + "example": "Store your keys and connect with the SSH agent for fast, encrypted authentication. Learn more about SSH agent" + }, + "newSshNudgeBodyTwo": { + "message": "Научете повече относно SSH-агента", + "description": "Two part message", + "example": "Store your keys and connect with the SSH agent for fast, encrypted authentication. Learn more about SSH agent" + }, + "generatorNudgeTitle": { + "message": "Създавайте пароли бързо" + }, + "generatorNudgeBodyOne": { + "message": "Създавайте лесно сложни и уникални пароли като щракнете върху", + "description": "Two part message", + "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." + }, + "generatorNudgeBodyTwo": { + "message": "за да защитите данните си за вписване.", + "description": "Two part message", + "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." + }, + "generatorNudgeBodyAria": { + "message": "Създавайте лесно сложни и уникални пароли като щракнете върху бутона за генериране на парола, за да защитите данните си за вписване.", + "description": "Aria label for the body content of the generator nudge" + }, + "noPermissionsViewPage": { + "message": "Нямате права за преглед на тази страница. Опитайте да се впишете с друг акаунт." } } diff --git a/apps/browser/src/_locales/bn/messages.json b/apps/browser/src/_locales/bn/messages.json index 275bb18e029..2e84549c710 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,55 @@ "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.", + "notificationNewItemAria": { + "message": "New Item, opens in new window", + "description": "Aria label for the new item button in notification bar confirmation message when error is prompted" + }, + "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": { - "content": "$1" + "label": { + "content": "$1", + "example": "Login" } - }, - "description": "Shown to user after login is updated." + } + }, + "notificationLoginSaveConfirmation": { + "message": "saved to Bitwarden.", + "description": "Shown to user after item is saved." + }, + "notificationLoginUpdatedConfirmation": { + "message": "updated in Bitwarden.", + "description": "Shown to user after item is updated." + }, + "selectItemAriaLabel": { + "message": "Select $ITEMTYPE$, $ITEMNAME$", + "description": "Used by screen readers. $1 is the item type (like vault or folder), $2 is the selected item name.", + "placeholders": { + "itemType": { + "content": "$1" + }, + "itemName": { + "content": "$2" + } + } }, "saveAsNewLoginAction": { "message": "Save as new login", @@ -1082,12 +1120,16 @@ "message": "Update login", "description": "Button text for updating an existing login entry." }, - "saveLoginPrompt": { - "message": "Save login?", + "unlockToSave": { + "message": "Unlock to save this login", + "description": "User prompt to take action in order to save the login they just entered." + }, + "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": { @@ -1323,8 +1365,8 @@ "featureUnavailable": { "message": "বৈশিষ্ট্য অনুপলব্ধ" }, - "encryptionKeyMigrationRequired": { - "message": "Encryption key migration required. Please login through the web vault to update your encryption key." + "legacyEncryptionUnsupported": { + "message": "Legacy encryption is no longer supported. Please contact support to recover your account." }, "premiumMembership": { "message": "প্রিমিয়াম সদস্য" @@ -1557,6 +1599,24 @@ "autofillSuggestionsSectionTitle": { "message": "Autofill suggestions" }, + "autofillSpotlightTitle": { + "message": "Easily find autofill suggestions" + }, + "autofillSpotlightDesc": { + "message": "Turn off your browser's autofill settings, so they don't conflict with Bitwarden." + }, + "turnOffBrowserAutofill": { + "message": "Turn off $BROWSER$ autofill", + "placeholders": { + "browser": { + "content": "$1", + "example": "Chrome" + } + } + }, + "turnOffAutofill": { + "message": "Turn off autofill" + }, "showInlineMenuLabel": { "message": "Show autofill suggestions on form fields" }, @@ -2144,6 +2204,9 @@ "useThisPassword": { "message": "Use this password" }, + "useThisPassphrase": { + "message": "Use this passphrase" + }, "useThisUsername": { "message": "Use this username" }, @@ -2167,15 +2230,6 @@ "vaultTimeoutAction1": { "message": "Timeout action" }, - "newCustomizationOptionsCalloutTitle": { - "message": "New customization options" - }, - "newCustomizationOptionsCalloutContent": { - "message": "Customize your vault experience with quick copy actions, compact mode, and more!" - }, - "newCustomizationOptionsCalloutLink": { - "message": "View all Appearance settings" - }, "lock": { "message": "লক", "description": "Verb form: to make secure or inaccessible by" @@ -2623,6 +2677,10 @@ "message": "All Sends", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, + "maxAccessCountReached": { + "message": "Max access count reached", + "description": "This text will be displayed after a Send has been accessed the maximum amount of times." + }, "hideTextByDefault": { "message": "Hide text by default" }, @@ -2963,14 +3021,14 @@ "copyCustomFieldNameNotUnique": { "message": "No unique identifier found." }, - "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.", - "placeholders": { - "organization": { - "content": "$1", - "example": "My Org Name" - } - } + "removeMasterPasswordForOrganizationUserKeyConnector": { + "message": "A master password is no longer required for members of the following organization. Please confirm the domain below with your organization administrator." + }, + "organizationName": { + "message": "Organization name" + }, + "keyConnectorDomain": { + "message": "Key Connector domain" }, "leaveOrganization": { "message": "Leave organization" @@ -3532,12 +3590,36 @@ "deviceTrusted": { "message": "Device trusted" }, - "sendsNoItemsTitle": { - "message": "No active Sends", + "trustOrganization": { + "message": "Trust organization" + }, + "trust": { + "message": "Trust" + }, + "doNotTrust": { + "message": "Do not trust" + }, + "organizationNotTrusted": { + "message": "Organization is not trusted" + }, + "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" + }, + "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." + }, + "orgTrustWarning1": { + "message": "This organization has an Enterprise policy that will enroll you in account recovery. Enrollment will allow organization administrators to change your password. Only proceed if you recognize this organization and the fingerprint phrase displayed below matches the organization's fingerprint." + }, + "trustUser": { + "message": "Trust user" + }, + "sendsTitleNoItems": { + "message": "Send sensitive information safely", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, - "sendsNoItemsMessage": { - "message": "Use Send to securely share encrypted information with anyone.", + "sendsBodyNoItems": { + "message": "Share files and data securely with anyone, on any platform. Your information will remain end-to-end encrypted while limiting exposure.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "inputRequired": { @@ -4456,6 +4538,33 @@ } } }, + "downloadBitwarden": { + "message": "Download Bitwarden" + }, + "downloadBitwardenOnAllDevices": { + "message": "Download Bitwarden on all devices" + }, + "getTheMobileApp": { + "message": "Get the mobile app" + }, + "getTheMobileAppDesc": { + "message": "Access your passwords on the go with the Bitwarden mobile app." + }, + "getTheDesktopApp": { + "message": "Get the desktop app" + }, + "getTheDesktopAppDesc": { + "message": "Access your vault without a browser, then set up unlock with biometrics to expedite unlocking in both the desktop app and browser extension." + }, + "downloadFromBitwardenNow": { + "message": "Download from bitwarden.com now" + }, + "getItOnGooglePlay": { + "message": "Get it on Google Play" + }, + "downloadOnTheAppStore": { + "message": "Download on the App Store" + }, "permanentlyDeleteAttachmentConfirmation": { "message": "Are you sure you want to permanently delete this attachment?" }, @@ -4917,6 +5026,15 @@ "biometricsStatusHelptextUnavailableReasonUnknown": { "message": "Biometric unlock is currently unavailable for an unknown reason." }, + "unlockVault": { + "message": "Unlock your vault in seconds" + }, + "unlockVaultDesc": { + "message": "You can customize your unlock and timeout settings to more quickly access your vault." + }, + "unlockPinSet": { + "message": "Unlock PIN set" + }, "authenticating": { "message": "Authenticating" }, @@ -4928,8 +5046,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": { @@ -5079,42 +5197,6 @@ "beta": { "message": "Beta" }, - "importantNotice": { - "message": "Important notice" - }, - "setupTwoStepLogin": { - "message": "Set up two-step login" - }, - "newDeviceVerificationNoticeContentPage1": { - "message": "Bitwarden will send a code to your account email to verify logins from new devices starting in February 2025." - }, - "newDeviceVerificationNoticeContentPage2": { - "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" - }, - "newDeviceVerificationNoticePageOneFormContent": { - "message": "Do you have reliable access to your email, $EMAIL$?", - "placeholders": { - "email": { - "content": "$1", - "example": "your_name@email.com" - } - } - }, - "newDeviceVerificationNoticePageOneEmailAccessNo": { - "message": "No, I do not" - }, - "newDeviceVerificationNoticePageOneEmailAccessYes": { - "message": "Yes, I can reliably access my email" - }, - "turnOnTwoStepLogin": { - "message": "Turn on two-step login" - }, - "changeAcctEmail": { - "message": "Change account email" - }, "extensionWidth": { "message": "Extension width" }, @@ -5169,6 +5251,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 +5283,98 @@ }, "secureDevicesBody": { "message": "Save unlimited passwords across unlimited devices with Bitwarden mobile, browser, and desktop apps." + }, + "nudgeBadgeAria": { + "message": "1 notification" + }, + "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!" + }, + "hasItemsVaultNudgeBodyOne": { + "message": "Autofill items for the current page" + }, + "hasItemsVaultNudgeBodyTwo": { + "message": "Favorite items for easy access" + }, + "hasItemsVaultNudgeBodyThree": { + "message": "Search your vault for something else" + }, + "newLoginNudgeTitle": { + "message": "Save time with autofill" + }, + "newLoginNudgeBodyOne": { + "message": "Include a", + "description": "This is in multiple parts to allow for bold text in the middle of the sentence.", + "example": "Include a Website so this login appears as an autofill suggestion." + }, + "newLoginNudgeBodyBold": { + "message": "Website", + "description": "This is in multiple parts to allow for bold text in the middle of the sentence.", + "example": "Include a Website so this login appears as an autofill suggestion." + }, + "newLoginNudgeBodyTwo": { + "message": "so this login appears as an autofill suggestion.", + "description": "This is in multiple parts to allow for bold text in the middle of the sentence.", + "example": "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" + }, + "newSshNudgeBodyOne": { + "message": "Store your keys and connect with the SSH agent for fast, encrypted authentication.", + "description": "Two part message", + "example": "Store your keys and connect with the SSH agent for fast, encrypted authentication. Learn more about SSH agent" + }, + "newSshNudgeBodyTwo": { + "message": "Learn more about SSH agent", + "description": "Two part message", + "example": "Store your keys and connect with the SSH agent for fast, encrypted authentication. Learn more about SSH agent" + }, + "generatorNudgeTitle": { + "message": "Quickly create passwords" + }, + "generatorNudgeBodyOne": { + "message": "Easily create strong and unique passwords by clicking on", + "description": "Two part message", + "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." + }, + "generatorNudgeBodyTwo": { + "message": "to help you keep your logins secure.", + "description": "Two part message", + "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." + }, + "generatorNudgeBodyAria": { + "message": "Easily create strong and unique passwords by clicking on the Generate password button to help you keep your logins secure.", + "description": "Aria label for the body content of the generator nudge" + }, + "noPermissionsViewPage": { + "message": "You do not have permissions to view this page. Try logging in with a different account." } } diff --git a/apps/browser/src/_locales/bs/messages.json b/apps/browser/src/_locales/bs/messages.json index 687610c777e..f23362e285a 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,55 @@ "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.", + "notificationNewItemAria": { + "message": "New Item, opens in new window", + "description": "Aria label for the new item button in notification bar confirmation message when error is prompted" + }, + "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": { - "content": "$1" + "label": { + "content": "$1", + "example": "Login" } - }, - "description": "Shown to user after login is updated." + } + }, + "notificationLoginSaveConfirmation": { + "message": "saved to Bitwarden.", + "description": "Shown to user after item is saved." + }, + "notificationLoginUpdatedConfirmation": { + "message": "updated in Bitwarden.", + "description": "Shown to user after item is updated." + }, + "selectItemAriaLabel": { + "message": "Select $ITEMTYPE$, $ITEMNAME$", + "description": "Used by screen readers. $1 is the item type (like vault or folder), $2 is the selected item name.", + "placeholders": { + "itemType": { + "content": "$1" + }, + "itemName": { + "content": "$2" + } + } }, "saveAsNewLoginAction": { "message": "Save as new login", @@ -1082,12 +1120,16 @@ "message": "Update login", "description": "Button text for updating an existing login entry." }, - "saveLoginPrompt": { - "message": "Save login?", + "unlockToSave": { + "message": "Unlock to save this login", + "description": "User prompt to take action in order to save the login they just entered." + }, + "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": { @@ -1323,8 +1365,8 @@ "featureUnavailable": { "message": "Feature unavailable" }, - "encryptionKeyMigrationRequired": { - "message": "Encryption key migration required. Please login through the web vault to update your encryption key." + "legacyEncryptionUnsupported": { + "message": "Legacy encryption is no longer supported. Please contact support to recover your account." }, "premiumMembership": { "message": "Premium membership" @@ -1557,6 +1599,24 @@ "autofillSuggestionsSectionTitle": { "message": "Autofill suggestions" }, + "autofillSpotlightTitle": { + "message": "Easily find autofill suggestions" + }, + "autofillSpotlightDesc": { + "message": "Turn off your browser's autofill settings, so they don't conflict with Bitwarden." + }, + "turnOffBrowserAutofill": { + "message": "Turn off $BROWSER$ autofill", + "placeholders": { + "browser": { + "content": "$1", + "example": "Chrome" + } + } + }, + "turnOffAutofill": { + "message": "Turn off autofill" + }, "showInlineMenuLabel": { "message": "Show autofill suggestions on form fields" }, @@ -2144,6 +2204,9 @@ "useThisPassword": { "message": "Use this password" }, + "useThisPassphrase": { + "message": "Use this passphrase" + }, "useThisUsername": { "message": "Use this username" }, @@ -2167,15 +2230,6 @@ "vaultTimeoutAction1": { "message": "Timeout action" }, - "newCustomizationOptionsCalloutTitle": { - "message": "New customization options" - }, - "newCustomizationOptionsCalloutContent": { - "message": "Customize your vault experience with quick copy actions, compact mode, and more!" - }, - "newCustomizationOptionsCalloutLink": { - "message": "View all Appearance settings" - }, "lock": { "message": "Zaključaj", "description": "Verb form: to make secure or inaccessible by" @@ -2623,6 +2677,10 @@ "message": "All Sends", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, + "maxAccessCountReached": { + "message": "Max access count reached", + "description": "This text will be displayed after a Send has been accessed the maximum amount of times." + }, "hideTextByDefault": { "message": "Hide text by default" }, @@ -2963,14 +3021,14 @@ "copyCustomFieldNameNotUnique": { "message": "No unique identifier found." }, - "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.", - "placeholders": { - "organization": { - "content": "$1", - "example": "My Org Name" - } - } + "removeMasterPasswordForOrganizationUserKeyConnector": { + "message": "A master password is no longer required for members of the following organization. Please confirm the domain below with your organization administrator." + }, + "organizationName": { + "message": "Organization name" + }, + "keyConnectorDomain": { + "message": "Key Connector domain" }, "leaveOrganization": { "message": "Leave organization" @@ -3532,12 +3590,36 @@ "deviceTrusted": { "message": "Device trusted" }, - "sendsNoItemsTitle": { - "message": "No active Sends", + "trustOrganization": { + "message": "Trust organization" + }, + "trust": { + "message": "Trust" + }, + "doNotTrust": { + "message": "Do not trust" + }, + "organizationNotTrusted": { + "message": "Organization is not trusted" + }, + "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" + }, + "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." + }, + "orgTrustWarning1": { + "message": "This organization has an Enterprise policy that will enroll you in account recovery. Enrollment will allow organization administrators to change your password. Only proceed if you recognize this organization and the fingerprint phrase displayed below matches the organization's fingerprint." + }, + "trustUser": { + "message": "Trust user" + }, + "sendsTitleNoItems": { + "message": "Send sensitive information safely", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, - "sendsNoItemsMessage": { - "message": "Use Send to securely share encrypted information with anyone.", + "sendsBodyNoItems": { + "message": "Share files and data securely with anyone, on any platform. Your information will remain end-to-end encrypted while limiting exposure.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "inputRequired": { @@ -4456,6 +4538,33 @@ } } }, + "downloadBitwarden": { + "message": "Download Bitwarden" + }, + "downloadBitwardenOnAllDevices": { + "message": "Download Bitwarden on all devices" + }, + "getTheMobileApp": { + "message": "Get the mobile app" + }, + "getTheMobileAppDesc": { + "message": "Access your passwords on the go with the Bitwarden mobile app." + }, + "getTheDesktopApp": { + "message": "Get the desktop app" + }, + "getTheDesktopAppDesc": { + "message": "Access your vault without a browser, then set up unlock with biometrics to expedite unlocking in both the desktop app and browser extension." + }, + "downloadFromBitwardenNow": { + "message": "Download from bitwarden.com now" + }, + "getItOnGooglePlay": { + "message": "Get it on Google Play" + }, + "downloadOnTheAppStore": { + "message": "Download on the App Store" + }, "permanentlyDeleteAttachmentConfirmation": { "message": "Are you sure you want to permanently delete this attachment?" }, @@ -4917,6 +5026,15 @@ "biometricsStatusHelptextUnavailableReasonUnknown": { "message": "Biometric unlock is currently unavailable for an unknown reason." }, + "unlockVault": { + "message": "Unlock your vault in seconds" + }, + "unlockVaultDesc": { + "message": "You can customize your unlock and timeout settings to more quickly access your vault." + }, + "unlockPinSet": { + "message": "Unlock PIN set" + }, "authenticating": { "message": "Authenticating" }, @@ -4928,8 +5046,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": { @@ -5079,42 +5197,6 @@ "beta": { "message": "Beta" }, - "importantNotice": { - "message": "Important notice" - }, - "setupTwoStepLogin": { - "message": "Set up two-step login" - }, - "newDeviceVerificationNoticeContentPage1": { - "message": "Bitwarden will send a code to your account email to verify logins from new devices starting in February 2025." - }, - "newDeviceVerificationNoticeContentPage2": { - "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" - }, - "newDeviceVerificationNoticePageOneFormContent": { - "message": "Do you have reliable access to your email, $EMAIL$?", - "placeholders": { - "email": { - "content": "$1", - "example": "your_name@email.com" - } - } - }, - "newDeviceVerificationNoticePageOneEmailAccessNo": { - "message": "No, I do not" - }, - "newDeviceVerificationNoticePageOneEmailAccessYes": { - "message": "Yes, I can reliably access my email" - }, - "turnOnTwoStepLogin": { - "message": "Turn on two-step login" - }, - "changeAcctEmail": { - "message": "Change account email" - }, "extensionWidth": { "message": "Extension width" }, @@ -5169,6 +5251,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 +5283,98 @@ }, "secureDevicesBody": { "message": "Save unlimited passwords across unlimited devices with Bitwarden mobile, browser, and desktop apps." + }, + "nudgeBadgeAria": { + "message": "1 notification" + }, + "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!" + }, + "hasItemsVaultNudgeBodyOne": { + "message": "Autofill items for the current page" + }, + "hasItemsVaultNudgeBodyTwo": { + "message": "Favorite items for easy access" + }, + "hasItemsVaultNudgeBodyThree": { + "message": "Search your vault for something else" + }, + "newLoginNudgeTitle": { + "message": "Save time with autofill" + }, + "newLoginNudgeBodyOne": { + "message": "Include a", + "description": "This is in multiple parts to allow for bold text in the middle of the sentence.", + "example": "Include a Website so this login appears as an autofill suggestion." + }, + "newLoginNudgeBodyBold": { + "message": "Website", + "description": "This is in multiple parts to allow for bold text in the middle of the sentence.", + "example": "Include a Website so this login appears as an autofill suggestion." + }, + "newLoginNudgeBodyTwo": { + "message": "so this login appears as an autofill suggestion.", + "description": "This is in multiple parts to allow for bold text in the middle of the sentence.", + "example": "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" + }, + "newSshNudgeBodyOne": { + "message": "Store your keys and connect with the SSH agent for fast, encrypted authentication.", + "description": "Two part message", + "example": "Store your keys and connect with the SSH agent for fast, encrypted authentication. Learn more about SSH agent" + }, + "newSshNudgeBodyTwo": { + "message": "Learn more about SSH agent", + "description": "Two part message", + "example": "Store your keys and connect with the SSH agent for fast, encrypted authentication. Learn more about SSH agent" + }, + "generatorNudgeTitle": { + "message": "Quickly create passwords" + }, + "generatorNudgeBodyOne": { + "message": "Easily create strong and unique passwords by clicking on", + "description": "Two part message", + "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." + }, + "generatorNudgeBodyTwo": { + "message": "to help you keep your logins secure.", + "description": "Two part message", + "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." + }, + "generatorNudgeBodyAria": { + "message": "Easily create strong and unique passwords by clicking on the Generate password button to help you keep your logins secure.", + "description": "Aria label for the body content of the generator nudge" + }, + "noPermissionsViewPage": { + "message": "You do not have permissions to view this page. Try logging in with a different account." } } diff --git a/apps/browser/src/_locales/ca/messages.json b/apps/browser/src/_locales/ca/messages.json index d9a24c5e473..e4105606aef 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)" @@ -186,7 +189,7 @@ "message": "Copia notes" }, "copy": { - "message": "Copy", + "message": "Copia", "description": "Copy to clipboard" }, "fill": { @@ -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" }, @@ -1037,10 +1043,10 @@ "message": "Llista els elements d'identitat de la pestanya de la pàgina per facilitar l'autoemplenat." }, "clickToAutofillOnVault": { - "message": "Feu clic als elements per emplenar automàticament a la vista de la caixa forta" + "message": "Feu clic als elements per emplenar automàticament en la vista de la caixa forta" }, "clickToAutofill": { - "message": "Click items in autofill suggestion to fill" + "message": "Feu clic als elements del suggeriment d'emplenament automàtic per omplir-los" }, "clearClipboard": { "message": "Buida el porta-retalls", @@ -1051,28 +1057,60 @@ "description": "Clipboard is the operating system thing where you copy/paste data to on your device." }, "notificationAddDesc": { - "message": "Bitwarden ha de recordar aquesta contrasenya per a vosaltres?" + "message": "Ha de recordar Bitwarden aquesta contrasenya per a vosaltres?" }, "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.", + "notificationNewItemAria": { + "message": "New Item, opens in new window", + "description": "Aria label for the new item button in notification bar confirmation message when error is prompted" + }, + "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": { - "content": "$1" + "label": { + "content": "$1", + "example": "Login" } - }, - "description": "Shown to user after login is updated." + } + }, + "notificationLoginSaveConfirmation": { + "message": "saved to Bitwarden.", + "description": "Shown to user after item is saved." + }, + "notificationLoginUpdatedConfirmation": { + "message": "updated in Bitwarden.", + "description": "Shown to user after item is updated." + }, + "selectItemAriaLabel": { + "message": "Select $ITEMTYPE$, $ITEMNAME$", + "description": "Used by screen readers. $1 is the item type (like vault or folder), $2 is the selected item name.", + "placeholders": { + "itemType": { + "content": "$1" + }, + "itemName": { + "content": "$2" + } + } }, "saveAsNewLoginAction": { "message": "Save as new login", @@ -1082,12 +1120,16 @@ "message": "Update login", "description": "Button text for updating an existing login entry." }, - "saveLoginPrompt": { - "message": "Save login?", + "unlockToSave": { + "message": "Unlock to save this login", + "description": "User prompt to take action in order to save the login they just entered." + }, + "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": { @@ -1323,8 +1365,8 @@ "featureUnavailable": { "message": "Característica no disponible" }, - "encryptionKeyMigrationRequired": { - "message": "Cal migrar la clau de xifratge. Inicieu la sessió a la caixa forta web per actualitzar la clau de xifratge." + "legacyEncryptionUnsupported": { + "message": "Legacy encryption is no longer supported. Please contact support to recover your account." }, "premiumMembership": { "message": "Subscripció Premium" @@ -1557,14 +1599,32 @@ "autofillSuggestionsSectionTitle": { "message": "Suggeriments d'emplenament automàtic" }, + "autofillSpotlightTitle": { + "message": "Easily find autofill suggestions" + }, + "autofillSpotlightDesc": { + "message": "Turn off your browser's autofill settings, so they don't conflict with Bitwarden." + }, + "turnOffBrowserAutofill": { + "message": "Turn off $BROWSER$ autofill", + "placeholders": { + "browser": { + "content": "$1", + "example": "Chrome" + } + } + }, + "turnOffAutofill": { + "message": "Turn off autofill" + }, "showInlineMenuLabel": { "message": "Mostra suggeriments d'emplenament automàtic als camps del formulari" }, "showInlineMenuIdentitiesLabel": { - "message": "Display identities as suggestions" + "message": "Mostra identitats com a suggeriments" }, "showInlineMenuCardsLabel": { - "message": "Display cards as suggestions" + "message": "Mostra targetes com a suggeriments" }, "showInlineMenuOnIconSelectionLabel": { "message": "Mostra suggeriments quan la icona està seleccionada" @@ -2144,6 +2204,9 @@ "useThisPassword": { "message": "Utilitzeu aquesta contrasenya" }, + "useThisPassphrase": { + "message": "Use this passphrase" + }, "useThisUsername": { "message": "Utilitzeu aquest nom d'usuari" }, @@ -2159,7 +2222,7 @@ "description": "This will be used as part of a larger sentence, broken up to include the generator icon. The full sentence will read 'Use the generator [GENERATOR_ICON] to create a strong unique password'" }, "vaultCustomization": { - "message": "Vault customization" + "message": "Personalització de la caixa forta" }, "vaultTimeoutAction": { "message": "Acció quan acabe el temps d'espera de la caixa forta" @@ -2167,15 +2230,6 @@ "vaultTimeoutAction1": { "message": "Acció després del temps d'espera" }, - "newCustomizationOptionsCalloutTitle": { - "message": "New customization options" - }, - "newCustomizationOptionsCalloutContent": { - "message": "Customize your vault experience with quick copy actions, compact mode, and more!" - }, - "newCustomizationOptionsCalloutLink": { - "message": "View all Appearance settings" - }, "lock": { "message": "Bloqueja", "description": "Verb form: to make secure or inaccessible by" @@ -2623,6 +2677,10 @@ "message": "Tots els Send", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, + "maxAccessCountReached": { + "message": "Max access count reached", + "description": "This text will be displayed after a Send has been accessed the maximum amount of times." + }, "hideTextByDefault": { "message": "Amaga el text per defecte" }, @@ -2963,14 +3021,14 @@ "copyCustomFieldNameNotUnique": { "message": "No s'ha trobat cap identificador únic." }, - "convertOrganizationEncryptionDesc": { - "message": "$ORGANIZATION$ està utilitzant SSO amb un servidor autoallotjat de claus. Ja no es requereix una contrasenya mestra d'inici de sessió per als membres d'aquesta organització.", - "placeholders": { - "organization": { - "content": "$1", - "example": "My Org Name" - } - } + "removeMasterPasswordForOrganizationUserKeyConnector": { + "message": "A master password is no longer required for members of the following organization. Please confirm the domain below with your organization administrator." + }, + "organizationName": { + "message": "Organization name" + }, + "keyConnectorDomain": { + "message": "Key Connector domain" }, "leaveOrganization": { "message": "Abandona l'organització" @@ -3532,12 +3590,36 @@ "deviceTrusted": { "message": "Dispositiu de confiança" }, - "sendsNoItemsTitle": { - "message": "No hi ha Sends actius", + "trustOrganization": { + "message": "Trust organization" + }, + "trust": { + "message": "Trust" + }, + "doNotTrust": { + "message": "Do not trust" + }, + "organizationNotTrusted": { + "message": "Organization is not trusted" + }, + "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" + }, + "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." + }, + "orgTrustWarning1": { + "message": "This organization has an Enterprise policy that will enroll you in account recovery. Enrollment will allow organization administrators to change your password. Only proceed if you recognize this organization and the fingerprint phrase displayed below matches the organization's fingerprint." + }, + "trustUser": { + "message": "Trust user" + }, + "sendsTitleNoItems": { + "message": "Send sensitive information safely", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, - "sendsNoItemsMessage": { - "message": "Utilitzeu Send per compartir informació xifrada de manera segura amb qualsevol persona.", + "sendsBodyNoItems": { + "message": "Share files and data securely with anyone, on any platform. Your information will remain end-to-end encrypted while limiting exposure.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "inputRequired": { @@ -4456,6 +4538,33 @@ } } }, + "downloadBitwarden": { + "message": "Download Bitwarden" + }, + "downloadBitwardenOnAllDevices": { + "message": "Download Bitwarden on all devices" + }, + "getTheMobileApp": { + "message": "Get the mobile app" + }, + "getTheMobileAppDesc": { + "message": "Access your passwords on the go with the Bitwarden mobile app." + }, + "getTheDesktopApp": { + "message": "Get the desktop app" + }, + "getTheDesktopAppDesc": { + "message": "Access your vault without a browser, then set up unlock with biometrics to expedite unlocking in both the desktop app and browser extension." + }, + "downloadFromBitwardenNow": { + "message": "Download from bitwarden.com now" + }, + "getItOnGooglePlay": { + "message": "Get it on Google Play" + }, + "downloadOnTheAppStore": { + "message": "Download on the App Store" + }, "permanentlyDeleteAttachmentConfirmation": { "message": "Esteu segur que voleu suprimir definitivament aquest adjunt?" }, @@ -4870,10 +4979,10 @@ "message": "Amaga el recompte de caràcters" }, "itemsInTrash": { - "message": "Items in trash" + "message": "Elements a la paperera" }, "noItemsInTrash": { - "message": "No items in trash" + "message": "No hi ha cap element a la paperera" }, "noItemsInTrashDesc": { "message": "Items you delete will appear here and be permanently deleted after 30 days" @@ -4917,6 +5026,15 @@ "biometricsStatusHelptextUnavailableReasonUnknown": { "message": "Biometric unlock is currently unavailable for an unknown reason." }, + "unlockVault": { + "message": "Unlock your vault in seconds" + }, + "unlockVaultDesc": { + "message": "You can customize your unlock and timeout settings to more quickly access your vault." + }, + "unlockPinSet": { + "message": "Unlock PIN set" + }, "authenticating": { "message": "S'està autenticant" }, @@ -4928,8 +5046,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": { @@ -5079,42 +5197,6 @@ "beta": { "message": "Beta" }, - "importantNotice": { - "message": "Noticia important" - }, - "setupTwoStepLogin": { - "message": "Set up two-step login" - }, - "newDeviceVerificationNoticeContentPage1": { - "message": "Bitwarden will send a code to your account email to verify logins from new devices starting in February 2025." - }, - "newDeviceVerificationNoticeContentPage2": { - "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": "Recorda-m'ho més tard" - }, - "newDeviceVerificationNoticePageOneFormContent": { - "message": "Do you have reliable access to your email, $EMAIL$?", - "placeholders": { - "email": { - "content": "$1", - "example": "your_name@email.com" - } - } - }, - "newDeviceVerificationNoticePageOneEmailAccessNo": { - "message": "No, jo no" - }, - "newDeviceVerificationNoticePageOneEmailAccessYes": { - "message": "Yes, I can reliably access my email" - }, - "turnOnTwoStepLogin": { - "message": "Activa l'inici de sessió en dos passos" - }, - "changeAcctEmail": { - "message": "Change account email" - }, "extensionWidth": { "message": "Amplada d'extensió" }, @@ -5169,6 +5251,12 @@ "changeAtRiskPassword": { "message": "Change at-risk password" }, + "settingsVaultOptions": { + "message": "Opcions de la caixa forta" + }, + "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 +5283,98 @@ }, "secureDevicesBody": { "message": "Save unlimited passwords across unlimited devices with Bitwarden mobile, browser, and desktop apps." + }, + "nudgeBadgeAria": { + "message": "1 notification" + }, + "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!" + }, + "hasItemsVaultNudgeBodyOne": { + "message": "Autofill items for the current page" + }, + "hasItemsVaultNudgeBodyTwo": { + "message": "Favorite items for easy access" + }, + "hasItemsVaultNudgeBodyThree": { + "message": "Search your vault for something else" + }, + "newLoginNudgeTitle": { + "message": "Save time with autofill" + }, + "newLoginNudgeBodyOne": { + "message": "Include a", + "description": "This is in multiple parts to allow for bold text in the middle of the sentence.", + "example": "Include a Website so this login appears as an autofill suggestion." + }, + "newLoginNudgeBodyBold": { + "message": "Website", + "description": "This is in multiple parts to allow for bold text in the middle of the sentence.", + "example": "Include a Website so this login appears as an autofill suggestion." + }, + "newLoginNudgeBodyTwo": { + "message": "so this login appears as an autofill suggestion.", + "description": "This is in multiple parts to allow for bold text in the middle of the sentence.", + "example": "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" + }, + "newSshNudgeBodyOne": { + "message": "Store your keys and connect with the SSH agent for fast, encrypted authentication.", + "description": "Two part message", + "example": "Store your keys and connect with the SSH agent for fast, encrypted authentication. Learn more about SSH agent" + }, + "newSshNudgeBodyTwo": { + "message": "Learn more about SSH agent", + "description": "Two part message", + "example": "Store your keys and connect with the SSH agent for fast, encrypted authentication. Learn more about SSH agent" + }, + "generatorNudgeTitle": { + "message": "Quickly create passwords" + }, + "generatorNudgeBodyOne": { + "message": "Easily create strong and unique passwords by clicking on", + "description": "Two part message", + "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." + }, + "generatorNudgeBodyTwo": { + "message": "to help you keep your logins secure.", + "description": "Two part message", + "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." + }, + "generatorNudgeBodyAria": { + "message": "Easily create strong and unique passwords by clicking on the Generate password button to help you keep your logins secure.", + "description": "Aria label for the body content of the generator nudge" + }, + "noPermissionsViewPage": { + "message": "You do not have permissions to view this page. Try logging in with a different account." } } diff --git a/apps/browser/src/_locales/cs/messages.json b/apps/browser/src/_locales/cs/messages.json index 2ed34c8c71a..0d7eb8082d2 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,55 @@ "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.", + "notificationNewItemAria": { + "message": "Nová položka, otevře se v novém okně", + "description": "Aria label for the new item button in notification bar confirmation message when error is prompted" + }, + "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": { - "content": "$1" + "label": { + "content": "$1", + "example": "Login" } - }, - "description": "Shown to user after login is updated." + } + }, + "notificationLoginSaveConfirmation": { + "message": "uloženo do Bitwardenu.", + "description": "Shown to user after item is saved." + }, + "notificationLoginUpdatedConfirmation": { + "message": "aktualizováno v Bitwardenu.", + "description": "Shown to user after item is updated." + }, + "selectItemAriaLabel": { + "message": "Vybrat $ITEMTYPE$, $ITEMNAME$", + "description": "Used by screen readers. $1 is the item type (like vault or folder), $2 is the selected item name.", + "placeholders": { + "itemType": { + "content": "$1" + }, + "itemName": { + "content": "$2" + } + } }, "saveAsNewLoginAction": { "message": "Uložit jako nové přihlašovací údaje", @@ -1082,12 +1120,16 @@ "message": "Aktualizovat přihlašovací údaje", "description": "Button text for updating an existing login entry." }, - "saveLoginPrompt": { - "message": "Uložit přihlašovací údaje?", + "unlockToSave": { + "message": "Odemknout pro uložení tohoto přihlášení", + "description": "User prompt to take action in order to save the login they just entered." + }, + "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": { @@ -1323,8 +1365,8 @@ "featureUnavailable": { "message": "Funkce je nedostupná" }, - "encryptionKeyMigrationRequired": { - "message": "Vyžaduje se migrace šifrovacího klíče. Pro aktualizaci šifrovacího klíče se přihlaste přes webový trezor." + "legacyEncryptionUnsupported": { + "message": "Staré šifrování již není podporováno. Kontaktujte podporu pro obnovení Vašeho účtu." }, "premiumMembership": { "message": "Prémiové členství" @@ -1557,6 +1599,24 @@ "autofillSuggestionsSectionTitle": { "message": "Návrhy automatického vyplňování" }, + "autofillSpotlightTitle": { + "message": "Snadné hledání návrhů automatického vyplňování" + }, + "autofillSpotlightDesc": { + "message": "Vypněte nastavení automatického vyplňování prohlížeče, takže nebude v rozporu s Bitwardenem." + }, + "turnOffBrowserAutofill": { + "message": "Vypnout automatické vyplňování $BROWSER$", + "placeholders": { + "browser": { + "content": "$1", + "example": "Chrome" + } + } + }, + "turnOffAutofill": { + "message": "Vypnout automatické vyplňování" + }, "showInlineMenuLabel": { "message": "Zobrazit návrhy automatického vyplňování v polích formuláře" }, @@ -2144,6 +2204,9 @@ "useThisPassword": { "message": "Použít toto heslo" }, + "useThisPassphrase": { + "message": "Použít tuto heslovou frázi" + }, "useThisUsername": { "message": "Použít toto uživatelské jméno" }, @@ -2167,15 +2230,6 @@ "vaultTimeoutAction1": { "message": "Akce vypršení časového limitu" }, - "newCustomizationOptionsCalloutTitle": { - "message": "Nové volby přizpůsobení" - }, - "newCustomizationOptionsCalloutContent": { - "message": "Přizpůsobte si svůj trezor s rychlými kopírovacími akcemi, kompaktním režimem a dalším!" - }, - "newCustomizationOptionsCalloutLink": { - "message": "Zobrazit všechna nastavení vzhledu" - }, "lock": { "message": "Zamknout", "description": "Verb form: to make secure or inaccessible by" @@ -2623,6 +2677,10 @@ "message": "Všechny Sends", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, + "maxAccessCountReached": { + "message": "Dosažen maximální počet přístupů", + "description": "This text will be displayed after a Send has been accessed the maximum amount of times." + }, "hideTextByDefault": { "message": "Ve výchozím nastavení skrýt text" }, @@ -2963,14 +3021,14 @@ "copyCustomFieldNameNotUnique": { "message": "Nenalezen žádný jedinečný identifikátor." }, - "convertOrganizationEncryptionDesc": { - "message": "$ORGANIZATION$ používá SSO s vlastním serverem s klíči. Hlavní heslo pro členy této organizace již není vyžadováno.", - "placeholders": { - "organization": { - "content": "$1", - "example": "My Org Name" - } - } + "removeMasterPasswordForOrganizationUserKeyConnector": { + "message": "Hlavní heslo již není vyžadováno pro členy následující organizace. Potvrďte níže uvedenou doménu u správce Vaší organizace." + }, + "organizationName": { + "message": "Název organizace" + }, + "keyConnectorDomain": { + "message": "Doména Key Connectoru" }, "leaveOrganization": { "message": "Opustit organizaci" @@ -3412,7 +3470,7 @@ "message": "Nastavení automatického vyplňování" }, "autofillKeyboardShortcutSectionTitle": { - "message": "Zkrátka automatického vyplňování" + "message": "Zkratka automatického vyplňování" }, "autofillKeyboardShortcutUpdateLabel": { "message": "Změnit zkratku" @@ -3532,12 +3590,36 @@ "deviceTrusted": { "message": "Zařízení zařazeno mezi důvěryhodné" }, - "sendsNoItemsTitle": { - "message": "Žádná aktivní Sends", + "trustOrganization": { + "message": "Důvěřovat organizaci" + }, + "trust": { + "message": "Důvěřovat" + }, + "doNotTrust": { + "message": "Nedůvěřovat" + }, + "organizationNotTrusted": { + "message": "Organizace není důvěryhodná" + }, + "emergencyAccessTrustWarning": { + "message": "Pro zabezpečení Vašeho účtu potvrďte jen v případě, že jste tomuto uživateli udělili nouzový přístup a jeho otisk prstu odpovídá tomu, co je zobrazeno v jeho účtu." + }, + "orgTrustWarning": { + "message": "Pro zabezpečení Vašeho účtu pokračujte jen v případě, že jste členem této organizace, máte povoleno obnovení účtu a zobrazený otisk prstu níže odpovídá otisku prstu organizace." + }, + "orgTrustWarning1": { + "message": "Tato organizace má firemní zásady, které Vás zapíšou do obnovy účtu. Zápis umožní správcům organizace změnit Vaše heslo. Pokračujte jen v případě, že znáte tuto organizaci a fráze otisku prstu zobrazenou níže odpovídá otisku prstu organizace." + }, + "trustUser": { + "message": "Důvěřovat uživateli" + }, + "sendsTitleNoItems": { + "message": "Posílejte citlivé informace bezpečně", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, - "sendsNoItemsMessage": { - "message": "Použijte Send pro bezpečné sdílení šifrovaných informací s kýmkoliv.", + "sendsBodyNoItems": { + "message": "Sdílejte bezpečně soubory a data s kýmkoli na libovolné platformě. Vaše informace zůstanou šifrovány a zároveň omezují expozici.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "inputRequired": { @@ -4456,6 +4538,33 @@ } } }, + "downloadBitwarden": { + "message": "Stáhnout Bitwarden" + }, + "downloadBitwardenOnAllDevices": { + "message": "Stáhnout Bitwarden na všech zařízeních" + }, + "getTheMobileApp": { + "message": "Získejte mobilní aplikaci" + }, + "getTheMobileAppDesc": { + "message": "Přistupujte k Vašim heslům pomocí mobilní aplikace Bitwarden." + }, + "getTheDesktopApp": { + "message": "Získejte desktopovou aplikaci" + }, + "getTheDesktopAppDesc": { + "message": "Přistupujte k Vašemu trezoru bez prohlížeče a poté nastavte odemknutí s biometrikou jak v desktopové aplikaci, tak v rozšíření prohlížeče." + }, + "downloadFromBitwardenNow": { + "message": "Stáhnout z bitwarden.com nyní" + }, + "getItOnGooglePlay": { + "message": "Získejte ji na Google Play" + }, + "downloadOnTheAppStore": { + "message": "Stáhnout v App Store" + }, "permanentlyDeleteAttachmentConfirmation": { "message": "Opravdu chcete tuto přílohu navždy smazat?" }, @@ -4917,6 +5026,15 @@ "biometricsStatusHelptextUnavailableReasonUnknown": { "message": "Biometrické odemknutí je momentálně z neznámého důvodu nedostupné." }, + "unlockVault": { + "message": "Rychlé odemknutí trezoru" + }, + "unlockVaultDesc": { + "message": "Pro rychlejší přístup k trezoru můžete upravit nastavení odemknutí a vypršení časového limitu." + }, + "unlockPinSet": { + "message": "PIN pro odemknutí byl nastaven" + }, "authenticating": { "message": "Ověřování" }, @@ -4928,8 +5046,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": { @@ -5079,42 +5197,6 @@ "beta": { "message": "Beta" }, - "importantNotice": { - "message": "Důležité upozornění" - }, - "setupTwoStepLogin": { - "message": "Nastavit dvoufázové přihlášení" - }, - "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." - }, - "newDeviceVerificationNoticeContentPage2": { - "message": "Dvoufázové přihlášení můžete nastavit jako alternativní způsob ochrany Vašeho účtu nebo změnit svůj e-mail na ten, k němuž můžete přistupovat." - }, - "remindMeLater": { - "message": "Připomenout později" - }, - "newDeviceVerificationNoticePageOneFormContent": { - "message": "Máte spolehlivý přístup ke svému e-mailu $EMAIL$?", - "placeholders": { - "email": { - "content": "$1", - "example": "your_name@email.com" - } - } - }, - "newDeviceVerificationNoticePageOneEmailAccessNo": { - "message": "Ne, nemám" - }, - "newDeviceVerificationNoticePageOneEmailAccessYes": { - "message": "Ano, ke svému e-mailu mám přístup" - }, - "turnOnTwoStepLogin": { - "message": "Zapnout dvoufázové přihlášení" - }, - "changeAcctEmail": { - "message": "Změnit e-mail účtu" - }, "extensionWidth": { "message": "Šířka rozšíření" }, @@ -5169,6 +5251,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 +5283,98 @@ }, "secureDevicesBody": { "message": "Uložte neomezená hesla na neomezených zařízeních s Bitwardenem na mobilu, prohlížeči a desktopové aplikaci." + }, + "nudgeBadgeAria": { + "message": "1 oznámení" + }, + "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!" + }, + "hasItemsVaultNudgeBodyOne": { + "message": "Položky automatického vyplňování aktuální stránky" + }, + "hasItemsVaultNudgeBodyTwo": { + "message": "Oblíbené položky pro snadný přístup" + }, + "hasItemsVaultNudgeBodyThree": { + "message": "Najít v trezoru něco jiného" + }, + "newLoginNudgeTitle": { + "message": "Ušetřete čas s automatickým vyplňováním" + }, + "newLoginNudgeBodyOne": { + "message": "Zahrne", + "description": "This is in multiple parts to allow for bold text in the middle of the sentence.", + "example": "Include a Website so this login appears as an autofill suggestion." + }, + "newLoginNudgeBodyBold": { + "message": "webovou stránku, ", + "description": "This is in multiple parts to allow for bold text in the middle of the sentence.", + "example": "Include a Website so this login appears as an autofill suggestion." + }, + "newLoginNudgeBodyTwo": { + "message": "takže se toto přihlášení objeví jako návrh automatického vyplňování.", + "description": "This is in multiple parts to allow for bold text in the middle of the sentence.", + "example": "Include a Website so this login appears as an autofill suggestion." + }, + "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" + }, + "newSshNudgeBodyOne": { + "message": "Uložte své klíče a připojte se k SSH agentovi pro rychlé a šifrované ověření.", + "description": "Two part message", + "example": "Store your keys and connect with the SSH agent for fast, encrypted authentication. Learn more about SSH agent" + }, + "newSshNudgeBodyTwo": { + "message": "Další informace o SSH agentovi", + "description": "Two part message", + "example": "Store your keys and connect with the SSH agent for fast, encrypted authentication. Learn more about SSH agent" + }, + "generatorNudgeTitle": { + "message": "Rychlé vytvoření hesla" + }, + "generatorNudgeBodyOne": { + "message": "Jednoduše vytvořte silná a unikátní hesla klepnutím na", + "description": "Two part message", + "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." + }, + "generatorNudgeBodyTwo": { + "message": "aby Vám pomohlo udržet Vaše přihlašovací údaje v bezpečí.", + "description": "Two part message", + "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." + }, + "generatorNudgeBodyAria": { + "message": "Jednoduše vytvořte silná a unikátní hesla klepnutím na tlačítko Generovat heslo, které Vám pomůže udržet Vaše přihlašovací údaje v bezpečí.", + "description": "Aria label for the body content of the generator nudge" + }, + "noPermissionsViewPage": { + "message": "Nemáte oprávnění k zobrazení této stránky. Zkuste se přihlásit jiným účtem." } } diff --git a/apps/browser/src/_locales/cy/messages.json b/apps/browser/src/_locales/cy/messages.json index eb07358540d..c842e8cf543 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,55 @@ "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.", + "notificationNewItemAria": { + "message": "New Item, opens in new window", + "description": "Aria label for the new item button in notification bar confirmation message when error is prompted" + }, + "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": { - "content": "$1" + "label": { + "content": "$1", + "example": "Login" } - }, - "description": "Shown to user after login is updated." + } + }, + "notificationLoginSaveConfirmation": { + "message": "saved to Bitwarden.", + "description": "Shown to user after item is saved." + }, + "notificationLoginUpdatedConfirmation": { + "message": "updated in Bitwarden.", + "description": "Shown to user after item is updated." + }, + "selectItemAriaLabel": { + "message": "Select $ITEMTYPE$, $ITEMNAME$", + "description": "Used by screen readers. $1 is the item type (like vault or folder), $2 is the selected item name.", + "placeholders": { + "itemType": { + "content": "$1" + }, + "itemName": { + "content": "$2" + } + } }, "saveAsNewLoginAction": { "message": "Cadw fel manylion mewngofnodi newydd", @@ -1082,12 +1120,16 @@ "message": "Update login", "description": "Button text for updating an existing login entry." }, - "saveLoginPrompt": { - "message": "Save login?", + "unlockToSave": { + "message": "Unlock to save this login", + "description": "User prompt to take action in order to save the login they just entered." + }, + "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": { @@ -1323,8 +1365,8 @@ "featureUnavailable": { "message": "Feature unavailable" }, - "encryptionKeyMigrationRequired": { - "message": "Encryption key migration required. Please login through the web vault to update your encryption key." + "legacyEncryptionUnsupported": { + "message": "Legacy encryption is no longer supported. Please contact support to recover your account." }, "premiumMembership": { "message": "Aelodaeth uwch" @@ -1557,6 +1599,24 @@ "autofillSuggestionsSectionTitle": { "message": "Autofill suggestions" }, + "autofillSpotlightTitle": { + "message": "Easily find autofill suggestions" + }, + "autofillSpotlightDesc": { + "message": "Turn off your browser's autofill settings, so they don't conflict with Bitwarden." + }, + "turnOffBrowserAutofill": { + "message": "Turn off $BROWSER$ autofill", + "placeholders": { + "browser": { + "content": "$1", + "example": "Chrome" + } + } + }, + "turnOffAutofill": { + "message": "Turn off autofill" + }, "showInlineMenuLabel": { "message": "Show autofill suggestions on form fields" }, @@ -2144,6 +2204,9 @@ "useThisPassword": { "message": "Defnyddio'r cyfrinair hwn" }, + "useThisPassphrase": { + "message": "Use this passphrase" + }, "useThisUsername": { "message": "Defnyddio'r enw defnyddiwr hwn" }, @@ -2167,15 +2230,6 @@ "vaultTimeoutAction1": { "message": "Timeout action" }, - "newCustomizationOptionsCalloutTitle": { - "message": "New customization options" - }, - "newCustomizationOptionsCalloutContent": { - "message": "Customize your vault experience with quick copy actions, compact mode, and more!" - }, - "newCustomizationOptionsCalloutLink": { - "message": "View all Appearance settings" - }, "lock": { "message": "Cloi", "description": "Verb form: to make secure or inaccessible by" @@ -2623,6 +2677,10 @@ "message": "Pob Send", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, + "maxAccessCountReached": { + "message": "Max access count reached", + "description": "This text will be displayed after a Send has been accessed the maximum amount of times." + }, "hideTextByDefault": { "message": "Hide text by default" }, @@ -2963,14 +3021,14 @@ "copyCustomFieldNameNotUnique": { "message": "No unique identifier found." }, - "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.", - "placeholders": { - "organization": { - "content": "$1", - "example": "My Org Name" - } - } + "removeMasterPasswordForOrganizationUserKeyConnector": { + "message": "A master password is no longer required for members of the following organization. Please confirm the domain below with your organization administrator." + }, + "organizationName": { + "message": "Organization name" + }, + "keyConnectorDomain": { + "message": "Key Connector domain" }, "leaveOrganization": { "message": "Leave organization" @@ -3532,12 +3590,36 @@ "deviceTrusted": { "message": "Device trusted" }, - "sendsNoItemsTitle": { - "message": "No active Sends", + "trustOrganization": { + "message": "Trust organization" + }, + "trust": { + "message": "Trust" + }, + "doNotTrust": { + "message": "Do not trust" + }, + "organizationNotTrusted": { + "message": "Organization is not trusted" + }, + "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" + }, + "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." + }, + "orgTrustWarning1": { + "message": "This organization has an Enterprise policy that will enroll you in account recovery. Enrollment will allow organization administrators to change your password. Only proceed if you recognize this organization and the fingerprint phrase displayed below matches the organization's fingerprint." + }, + "trustUser": { + "message": "Trust user" + }, + "sendsTitleNoItems": { + "message": "Send sensitive information safely", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, - "sendsNoItemsMessage": { - "message": "Use Send to securely share encrypted information with anyone.", + "sendsBodyNoItems": { + "message": "Share files and data securely with anyone, on any platform. Your information will remain end-to-end encrypted while limiting exposure.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "inputRequired": { @@ -4456,6 +4538,33 @@ } } }, + "downloadBitwarden": { + "message": "Download Bitwarden" + }, + "downloadBitwardenOnAllDevices": { + "message": "Download Bitwarden on all devices" + }, + "getTheMobileApp": { + "message": "Get the mobile app" + }, + "getTheMobileAppDesc": { + "message": "Access your passwords on the go with the Bitwarden mobile app." + }, + "getTheDesktopApp": { + "message": "Get the desktop app" + }, + "getTheDesktopAppDesc": { + "message": "Access your vault without a browser, then set up unlock with biometrics to expedite unlocking in both the desktop app and browser extension." + }, + "downloadFromBitwardenNow": { + "message": "Download from bitwarden.com now" + }, + "getItOnGooglePlay": { + "message": "Get it on Google Play" + }, + "downloadOnTheAppStore": { + "message": "Download on the App Store" + }, "permanentlyDeleteAttachmentConfirmation": { "message": "Are you sure you want to permanently delete this attachment?" }, @@ -4917,6 +5026,15 @@ "biometricsStatusHelptextUnavailableReasonUnknown": { "message": "Biometric unlock is currently unavailable for an unknown reason." }, + "unlockVault": { + "message": "Unlock your vault in seconds" + }, + "unlockVaultDesc": { + "message": "You can customize your unlock and timeout settings to more quickly access your vault." + }, + "unlockPinSet": { + "message": "Unlock PIN set" + }, "authenticating": { "message": "Authenticating" }, @@ -4928,8 +5046,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": { @@ -5079,42 +5197,6 @@ "beta": { "message": "Beta" }, - "importantNotice": { - "message": "Important notice" - }, - "setupTwoStepLogin": { - "message": "Set up two-step login" - }, - "newDeviceVerificationNoticeContentPage1": { - "message": "Bitwarden will send a code to your account email to verify logins from new devices starting in February 2025." - }, - "newDeviceVerificationNoticeContentPage2": { - "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" - }, - "newDeviceVerificationNoticePageOneFormContent": { - "message": "A oes gennych chi fynediad dibynadwy i'ch ebost, $EMAIL$?", - "placeholders": { - "email": { - "content": "$1", - "example": "your_name@email.com" - } - } - }, - "newDeviceVerificationNoticePageOneEmailAccessNo": { - "message": "Nac oes" - }, - "newDeviceVerificationNoticePageOneEmailAccessYes": { - "message": "Oes, mae gen i fynediad dibynadwy i fy ebost" - }, - "turnOnTwoStepLogin": { - "message": "Turn on two-step login" - }, - "changeAcctEmail": { - "message": "Change account email" - }, "extensionWidth": { "message": "Extension width" }, @@ -5169,6 +5251,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 +5283,98 @@ }, "secureDevicesBody": { "message": "Save unlimited passwords across unlimited devices with Bitwarden mobile, browser, and desktop apps." + }, + "nudgeBadgeAria": { + "message": "1 notification" + }, + "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!" + }, + "hasItemsVaultNudgeBodyOne": { + "message": "Autofill items for the current page" + }, + "hasItemsVaultNudgeBodyTwo": { + "message": "Favorite items for easy access" + }, + "hasItemsVaultNudgeBodyThree": { + "message": "Search your vault for something else" + }, + "newLoginNudgeTitle": { + "message": "Save time with autofill" + }, + "newLoginNudgeBodyOne": { + "message": "Include a", + "description": "This is in multiple parts to allow for bold text in the middle of the sentence.", + "example": "Include a Website so this login appears as an autofill suggestion." + }, + "newLoginNudgeBodyBold": { + "message": "Website", + "description": "This is in multiple parts to allow for bold text in the middle of the sentence.", + "example": "Include a Website so this login appears as an autofill suggestion." + }, + "newLoginNudgeBodyTwo": { + "message": "so this login appears as an autofill suggestion.", + "description": "This is in multiple parts to allow for bold text in the middle of the sentence.", + "example": "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" + }, + "newSshNudgeBodyOne": { + "message": "Store your keys and connect with the SSH agent for fast, encrypted authentication.", + "description": "Two part message", + "example": "Store your keys and connect with the SSH agent for fast, encrypted authentication. Learn more about SSH agent" + }, + "newSshNudgeBodyTwo": { + "message": "Learn more about SSH agent", + "description": "Two part message", + "example": "Store your keys and connect with the SSH agent for fast, encrypted authentication. Learn more about SSH agent" + }, + "generatorNudgeTitle": { + "message": "Quickly create passwords" + }, + "generatorNudgeBodyOne": { + "message": "Easily create strong and unique passwords by clicking on", + "description": "Two part message", + "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." + }, + "generatorNudgeBodyTwo": { + "message": "to help you keep your logins secure.", + "description": "Two part message", + "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." + }, + "generatorNudgeBodyAria": { + "message": "Easily create strong and unique passwords by clicking on the Generate password button to help you keep your logins secure.", + "description": "Aria label for the body content of the generator nudge" + }, + "noPermissionsViewPage": { + "message": "You do not have permissions to view this page. Try logging in with a different account." } } diff --git a/apps/browser/src/_locales/da/messages.json b/apps/browser/src/_locales/da/messages.json index 914c74411e4..71723b90283 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,55 @@ "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.", + "notificationNewItemAria": { + "message": "New Item, opens in new window", + "description": "Aria label for the new item button in notification bar confirmation message when error is prompted" + }, + "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": { - "content": "$1" + "label": { + "content": "$1", + "example": "Login" } - }, - "description": "Shown to user after login is updated." + } + }, + "notificationLoginSaveConfirmation": { + "message": "saved to Bitwarden.", + "description": "Shown to user after item is saved." + }, + "notificationLoginUpdatedConfirmation": { + "message": "updated in Bitwarden.", + "description": "Shown to user after item is updated." + }, + "selectItemAriaLabel": { + "message": "Select $ITEMTYPE$, $ITEMNAME$", + "description": "Used by screen readers. $1 is the item type (like vault or folder), $2 is the selected item name.", + "placeholders": { + "itemType": { + "content": "$1" + }, + "itemName": { + "content": "$2" + } + } }, "saveAsNewLoginAction": { "message": "Save as new login", @@ -1082,12 +1120,16 @@ "message": "Update login", "description": "Button text for updating an existing login entry." }, - "saveLoginPrompt": { - "message": "Save login?", + "unlockToSave": { + "message": "Unlock to save this login", + "description": "User prompt to take action in order to save the login they just entered." + }, + "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": { @@ -1323,8 +1365,8 @@ "featureUnavailable": { "message": "Funktion ikke tilgængelig" }, - "encryptionKeyMigrationRequired": { - "message": "Krypteringsnøglemigrering nødvendig. Log ind gennem web-boksen for at opdatere krypteringsnøglen." + "legacyEncryptionUnsupported": { + "message": "Legacy encryption is no longer supported. Please contact support to recover your account." }, "premiumMembership": { "message": "Premium-medlemskab" @@ -1557,6 +1599,24 @@ "autofillSuggestionsSectionTitle": { "message": "Autoudfyldningsforslag" }, + "autofillSpotlightTitle": { + "message": "Easily find autofill suggestions" + }, + "autofillSpotlightDesc": { + "message": "Turn off your browser's autofill settings, so they don't conflict with Bitwarden." + }, + "turnOffBrowserAutofill": { + "message": "Turn off $BROWSER$ autofill", + "placeholders": { + "browser": { + "content": "$1", + "example": "Chrome" + } + } + }, + "turnOffAutofill": { + "message": "Turn off autofill" + }, "showInlineMenuLabel": { "message": "Vis autoudfyld-menu i formularfelter" }, @@ -2144,6 +2204,9 @@ "useThisPassword": { "message": "Anvend denne adgangskode" }, + "useThisPassphrase": { + "message": "Use this passphrase" + }, "useThisUsername": { "message": "Anvend dette brugernavn" }, @@ -2167,15 +2230,6 @@ "vaultTimeoutAction1": { "message": "Timeouthandling" }, - "newCustomizationOptionsCalloutTitle": { - "message": "New customization options" - }, - "newCustomizationOptionsCalloutContent": { - "message": "Customize your vault experience with quick copy actions, compact mode, and more!" - }, - "newCustomizationOptionsCalloutLink": { - "message": "View all Appearance settings" - }, "lock": { "message": "Lås", "description": "Verb form: to make secure or inaccessible by" @@ -2623,6 +2677,10 @@ "message": "Alle Send", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, + "maxAccessCountReached": { + "message": "Max access count reached", + "description": "This text will be displayed after a Send has been accessed the maximum amount of times." + }, "hideTextByDefault": { "message": "Skjul tekst som standard" }, @@ -2963,14 +3021,14 @@ "copyCustomFieldNameNotUnique": { "message": "Ingen entydig identifikator fundet." }, - "convertOrganizationEncryptionDesc": { - "message": "$ORGANIZATION$ bruger SSO med en selv-hostet nøgleserver. En hovedadgangskode er ikke længere påkrævet for at logge ind for medlemmer af denne organisation.", - "placeholders": { - "organization": { - "content": "$1", - "example": "My Org Name" - } - } + "removeMasterPasswordForOrganizationUserKeyConnector": { + "message": "A master password is no longer required for members of the following organization. Please confirm the domain below with your organization administrator." + }, + "organizationName": { + "message": "Organization name" + }, + "keyConnectorDomain": { + "message": "Key Connector domain" }, "leaveOrganization": { "message": "Forlad organisation" @@ -3532,12 +3590,36 @@ "deviceTrusted": { "message": "Enhed betroet" }, - "sendsNoItemsTitle": { - "message": "Ingen aktive Sends", + "trustOrganization": { + "message": "Trust organization" + }, + "trust": { + "message": "Trust" + }, + "doNotTrust": { + "message": "Do not trust" + }, + "organizationNotTrusted": { + "message": "Organization is not trusted" + }, + "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" + }, + "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." + }, + "orgTrustWarning1": { + "message": "This organization has an Enterprise policy that will enroll you in account recovery. Enrollment will allow organization administrators to change your password. Only proceed if you recognize this organization and the fingerprint phrase displayed below matches the organization's fingerprint." + }, + "trustUser": { + "message": "Trust user" + }, + "sendsTitleNoItems": { + "message": "Send sensitive information safely", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, - "sendsNoItemsMessage": { - "message": "Brug Send til at dele krypterede oplysninger sikkert med nogen.", + "sendsBodyNoItems": { + "message": "Share files and data securely with anyone, on any platform. Your information will remain end-to-end encrypted while limiting exposure.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "inputRequired": { @@ -4456,6 +4538,33 @@ } } }, + "downloadBitwarden": { + "message": "Download Bitwarden" + }, + "downloadBitwardenOnAllDevices": { + "message": "Download Bitwarden on all devices" + }, + "getTheMobileApp": { + "message": "Get the mobile app" + }, + "getTheMobileAppDesc": { + "message": "Access your passwords on the go with the Bitwarden mobile app." + }, + "getTheDesktopApp": { + "message": "Get the desktop app" + }, + "getTheDesktopAppDesc": { + "message": "Access your vault without a browser, then set up unlock with biometrics to expedite unlocking in both the desktop app and browser extension." + }, + "downloadFromBitwardenNow": { + "message": "Download from bitwarden.com now" + }, + "getItOnGooglePlay": { + "message": "Get it on Google Play" + }, + "downloadOnTheAppStore": { + "message": "Download on the App Store" + }, "permanentlyDeleteAttachmentConfirmation": { "message": "Sikker på, at denne vedhæftning skal slettes permanent?" }, @@ -4917,6 +5026,15 @@ "biometricsStatusHelptextUnavailableReasonUnknown": { "message": "Biometrisk oplåsning er p.t. utilgængelig grundet en ukendt årsag." }, + "unlockVault": { + "message": "Unlock your vault in seconds" + }, + "unlockVaultDesc": { + "message": "You can customize your unlock and timeout settings to more quickly access your vault." + }, + "unlockPinSet": { + "message": "Unlock PIN set" + }, "authenticating": { "message": "Godkender" }, @@ -4928,8 +5046,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": { @@ -5079,42 +5197,6 @@ "beta": { "message": "Beta" }, - "importantNotice": { - "message": "Vigtig notits" - }, - "setupTwoStepLogin": { - "message": "Opsæt totrins-login" - }, - "newDeviceVerificationNoticeContentPage1": { - "message": "Startende i februar 2025, sender Bitwarden en kode til kontoe-mailadressen for at bekræfte logins fra nye enheder." - }, - "newDeviceVerificationNoticeContentPage2": { - "message": "Man kan opsætte totrins-login som en alternativ måde at beskytte sin konto på eller ændre sin e-mail til en, man kan tilgå." - }, - "remindMeLater": { - "message": "Påmind senere" - }, - "newDeviceVerificationNoticePageOneFormContent": { - "message": "Er der pålidelig adgang til e-mailadressen, $EMAIL$?", - "placeholders": { - "email": { - "content": "$1", - "example": "your_name@email.com" - } - } - }, - "newDeviceVerificationNoticePageOneEmailAccessNo": { - "message": "Nej, jeg gør ikke" - }, - "newDeviceVerificationNoticePageOneEmailAccessYes": { - "message": "Ja, e-mailadressen kan pålideligt tilgås" - }, - "turnOnTwoStepLogin": { - "message": "Slå totrins-login til" - }, - "changeAcctEmail": { - "message": "Skift kontoe-mailadresse" - }, "extensionWidth": { "message": "Udvidelsesbredde" }, @@ -5169,6 +5251,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 +5283,98 @@ }, "secureDevicesBody": { "message": "Save unlimited passwords across unlimited devices with Bitwarden mobile, browser, and desktop apps." + }, + "nudgeBadgeAria": { + "message": "1 notification" + }, + "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!" + }, + "hasItemsVaultNudgeBodyOne": { + "message": "Autofill items for the current page" + }, + "hasItemsVaultNudgeBodyTwo": { + "message": "Favorite items for easy access" + }, + "hasItemsVaultNudgeBodyThree": { + "message": "Search your vault for something else" + }, + "newLoginNudgeTitle": { + "message": "Save time with autofill" + }, + "newLoginNudgeBodyOne": { + "message": "Include a", + "description": "This is in multiple parts to allow for bold text in the middle of the sentence.", + "example": "Include a Website so this login appears as an autofill suggestion." + }, + "newLoginNudgeBodyBold": { + "message": "Website", + "description": "This is in multiple parts to allow for bold text in the middle of the sentence.", + "example": "Include a Website so this login appears as an autofill suggestion." + }, + "newLoginNudgeBodyTwo": { + "message": "so this login appears as an autofill suggestion.", + "description": "This is in multiple parts to allow for bold text in the middle of the sentence.", + "example": "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" + }, + "newSshNudgeBodyOne": { + "message": "Store your keys and connect with the SSH agent for fast, encrypted authentication.", + "description": "Two part message", + "example": "Store your keys and connect with the SSH agent for fast, encrypted authentication. Learn more about SSH agent" + }, + "newSshNudgeBodyTwo": { + "message": "Learn more about SSH agent", + "description": "Two part message", + "example": "Store your keys and connect with the SSH agent for fast, encrypted authentication. Learn more about SSH agent" + }, + "generatorNudgeTitle": { + "message": "Quickly create passwords" + }, + "generatorNudgeBodyOne": { + "message": "Easily create strong and unique passwords by clicking on", + "description": "Two part message", + "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." + }, + "generatorNudgeBodyTwo": { + "message": "to help you keep your logins secure.", + "description": "Two part message", + "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." + }, + "generatorNudgeBodyAria": { + "message": "Easily create strong and unique passwords by clicking on the Generate password button to help you keep your logins secure.", + "description": "Aria label for the body content of the generator nudge" + }, + "noPermissionsViewPage": { + "message": "You do not have permissions to view this page. Try logging in with a different account." } } diff --git a/apps/browser/src/_locales/de/messages.json b/apps/browser/src/_locales/de/messages.json index 0edf5edfa3d..28402576ddf 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)" @@ -617,7 +620,7 @@ } }, "website": { - "message": "Webseite" + "message": "Website" }, "toggleVisibility": { "message": "Sichtbarkeit umschalten" @@ -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,55 @@ "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.", + "notificationNewItemAria": { + "message": "Neuer Eintrag, öffnet sich in neuem Fenster", + "description": "Aria label for the new item button in notification bar confirmation message when error is prompted" + }, + "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": { - "content": "$1" + "label": { + "content": "$1", + "example": "Login" } - }, - "description": "Shown to user after login is updated." + } + }, + "notificationLoginSaveConfirmation": { + "message": "in Bitwarden gespeichert.", + "description": "Shown to user after item is saved." + }, + "notificationLoginUpdatedConfirmation": { + "message": "in Bitwarden aktualisiert.", + "description": "Shown to user after item is updated." + }, + "selectItemAriaLabel": { + "message": "$ITEMTYPE$, $ITEMNAME$ auswählen", + "description": "Used by screen readers. $1 is the item type (like vault or folder), $2 is the selected item name.", + "placeholders": { + "itemType": { + "content": "$1" + }, + "itemName": { + "content": "$2" + } + } }, "saveAsNewLoginAction": { "message": "Als neue Zugangsdaten speichern", @@ -1082,12 +1120,16 @@ "message": "Zugangsdaten aktualisieren", "description": "Button text for updating an existing login entry." }, - "saveLoginPrompt": { - "message": "Zugangsdaten speichern?", + "unlockToSave": { + "message": "Entsperren, um diese Zugangsdaten zu speichern", + "description": "User prompt to take action in order to save the login they just entered." + }, + "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 +1141,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 +1150,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 +1162,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": { @@ -1323,8 +1365,8 @@ "featureUnavailable": { "message": "Funktion nicht verfügbar" }, - "encryptionKeyMigrationRequired": { - "message": "Verschlüsselungscode-Migration erforderlich. Bitte melde dich über den Web-Tresor an, um deinen Verschlüsselungscode zu aktualisieren." + "legacyEncryptionUnsupported": { + "message": "Legacy encryption is no longer supported. Please contact support to recover your account." }, "premiumMembership": { "message": "Premium-Mitgliedschaft" @@ -1557,6 +1599,24 @@ "autofillSuggestionsSectionTitle": { "message": "Vorschläge zum Auto-Ausfüllen" }, + "autofillSpotlightTitle": { + "message": "Auto-Ausfüllen-Vorschläge einfach finden" + }, + "autofillSpotlightDesc": { + "message": "Deaktiviere die Auto-Ausfüllen-Einstellungen deines Browsers, damit sie nicht mit Bitwarden in Konflikt geraten." + }, + "turnOffBrowserAutofill": { + "message": "$BROWSER$ Auto-Ausfüllen deaktivieren", + "placeholders": { + "browser": { + "content": "$1", + "example": "Chrome" + } + } + }, + "turnOffAutofill": { + "message": "Auto-Ausfüllen deaktivieren" + }, "showInlineMenuLabel": { "message": "Vorschläge zum Auto-Ausfüllen in Formularfeldern anzeigen" }, @@ -2144,6 +2204,9 @@ "useThisPassword": { "message": "Dieses Passwort verwenden" }, + "useThisPassphrase": { + "message": "Diese Passphrase verwenden" + }, "useThisUsername": { "message": "Diesen Benutzernamen verwenden" }, @@ -2167,15 +2230,6 @@ "vaultTimeoutAction1": { "message": "Timeout-Aktion" }, - "newCustomizationOptionsCalloutTitle": { - "message": "Neue Personalisierungs-Optionen" - }, - "newCustomizationOptionsCalloutContent": { - "message": "Personalisiere deinen Tresor mit Schnellkopier-Aktionen, Kompaktmodus und mehr!" - }, - "newCustomizationOptionsCalloutLink": { - "message": "Alle Aussehen-Einstellungen anzeigen" - }, "lock": { "message": "Sperren", "description": "Verb form: to make secure or inaccessible by" @@ -2623,6 +2677,10 @@ "message": "Alle Sends", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, + "maxAccessCountReached": { + "message": "Max access count reached", + "description": "This text will be displayed after a Send has been accessed the maximum amount of times." + }, "hideTextByDefault": { "message": "Text standardmäßig ausblenden" }, @@ -2963,14 +3021,14 @@ "copyCustomFieldNameNotUnique": { "message": "Keine eindeutige Kennung gefunden." }, - "convertOrganizationEncryptionDesc": { - "message": "$ORGANIZATION$ verwendet SSO mit einem selbst gehosteten Schlüsselserver. Ein Master-Passwort ist nicht mehr erforderlich, damit sich Mitglieder dieser Organisation anmelden können.", - "placeholders": { - "organization": { - "content": "$1", - "example": "My Org Name" - } - } + "removeMasterPasswordForOrganizationUserKeyConnector": { + "message": "Für Mitglieder der folgenden Organisation ist kein Master-Passwort mehr erforderlich. Bitte bestätige die folgende Domain bei deinem Organisations-Administrator." + }, + "organizationName": { + "message": "Name der Organisation" + }, + "keyConnectorDomain": { + "message": "Key Connector-Domain" }, "leaveOrganization": { "message": "Organisation verlassen" @@ -3141,7 +3199,7 @@ "description": "Displayed with the address on the forwarding service's configuration screen." }, "forwarderGeneratedByWithWebsite": { - "message": "Webseite: $WEBSITE$. Von Bitwarden generiert.", + "message": "Website: $WEBSITE$. Von Bitwarden generiert.", "description": "Displayed with the address on the forwarding service's configuration screen.", "placeholders": { "WEBSITE": { @@ -3532,12 +3590,36 @@ "deviceTrusted": { "message": "Gerät wird vertraut" }, - "sendsNoItemsTitle": { - "message": "Keine aktiven Sends", + "trustOrganization": { + "message": "Organisation vertrauen" + }, + "trust": { + "message": "Vertrauen" + }, + "doNotTrust": { + "message": "Nicht vertrauen" + }, + "organizationNotTrusted": { + "message": "Organisation ist nicht vertrauenswürdig" + }, + "emergencyAccessTrustWarning": { + "message": "Bestätige zur Sicherheit deines Kontos nur, wenn du den Notfallzugriff diesem Benutzer gewährt hast und sein Fingerabdruck mit dem übereinstimmt, was in seinem Konto angezeigt wird" + }, + "orgTrustWarning": { + "message": "Fahre zur Sicherheit deines Kontos nur fort, wenn du ein Mitglied dieser Organisation bist, die Kontowiederherstellung aktiviert hast und der unten angezeigte Fingerabdruck mit dem Fingerabdruck der Organisation übereinstimmt." + }, + "orgTrustWarning1": { + "message": "Diese Organisation hat eine Unternehmensrichtlinie, die dich für die Kontowiederherstellung registriert. Die Registrierung wird es den Administratoren der Organisation erlauben, dein Passwort zu ändern. Fahre nur fort, wenn du diese Organisation kennst und die unten angezeigte Fingerabdruck-Phrase mit der der Organisation übereinstimmt." + }, + "trustUser": { + "message": "Benutzer vertrauen" + }, + "sendsTitleNoItems": { + "message": "Sensible Informationen sicher versenden", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, - "sendsNoItemsMessage": { - "message": "Verwende Send, um verschlüsselte Informationen sicher mit anderen zu teilen.", + "sendsBodyNoItems": { + "message": "Teile Dateien und Daten sicher mit jedem auf jeder Plattform. Deine Informationen bleiben Ende-zu-Ende-verschlüsselt, während die Verbreitung begrenzt wird.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "inputRequired": { @@ -4456,6 +4538,33 @@ } } }, + "downloadBitwarden": { + "message": "Bitwarden herunterladen" + }, + "downloadBitwardenOnAllDevices": { + "message": "Bitwarden auf allen Geräten herunterladen" + }, + "getTheMobileApp": { + "message": "Smartphone-App herunterladen" + }, + "getTheMobileAppDesc": { + "message": "Greife von unterwegs mit der Bitwarden Smartphone-App auf deine Passwörter zu." + }, + "getTheDesktopApp": { + "message": "Desktop-App herunterladen" + }, + "getTheDesktopAppDesc": { + "message": "Greife ohne Browser auf deinen Tresor zu und richte dann das Entsperren mit Biometrie ein, um die Entsperrung sowohl in der Desktop-App als auch in der Browser-Erweiterung zu beschleunigen." + }, + "downloadFromBitwardenNow": { + "message": "Jetzt von bitwarden.com herunterladen" + }, + "getItOnGooglePlay": { + "message": "Verfügbar bei Google Play" + }, + "downloadOnTheAppStore": { + "message": "Im App Store herunterladen" + }, "permanentlyDeleteAttachmentConfirmation": { "message": "Bist du sicher, dass du diesen Anhang dauerhaft löschen möchtest?" }, @@ -4917,6 +5026,15 @@ "biometricsStatusHelptextUnavailableReasonUnknown": { "message": "Biometrisches Entsperren ist derzeit aus einem unbekannten Grund nicht verfügbar." }, + "unlockVault": { + "message": "Entsperre deinen Tresor in Sekunden" + }, + "unlockVaultDesc": { + "message": "Du kannst deine Entsperr- und Timeout-Einstellungen anpassen, um schneller auf deinen Tresor zuzugreifen." + }, + "unlockPinSet": { + "message": "Entsperr-PIN festgelegt" + }, "authenticating": { "message": "Authentifizierung" }, @@ -4928,8 +5046,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": { @@ -5079,42 +5197,6 @@ "beta": { "message": "Beta" }, - "importantNotice": { - "message": "Wichtiger Hinweis" - }, - "setupTwoStepLogin": { - "message": "Zwei-Faktor-Authentifizierung einrichten" - }, - "newDeviceVerificationNoticeContentPage1": { - "message": "Ab Februar 2025 wird Bitwarden einen Code an deine Konto-E-Mail-Adresse senden, um Anmeldungen von neuen Geräten zu verifizieren." - }, - "newDeviceVerificationNoticeContentPage2": { - "message": "Du kannst die Zwei-Faktor-Authentifizierung als eine alternative Methode einrichten, um dein Konto zu schützen, oder deine E-Mail-Adresse zu einer anderen ändern, auf die du zugreifen kannst." - }, - "remindMeLater": { - "message": "Erinnere mich später" - }, - "newDeviceVerificationNoticePageOneFormContent": { - "message": "Hast du zuverlässigen Zugriff auf deine E-Mail-Adresse $EMAIL$?", - "placeholders": { - "email": { - "content": "$1", - "example": "your_name@email.com" - } - } - }, - "newDeviceVerificationNoticePageOneEmailAccessNo": { - "message": "Nein, habe ich nicht" - }, - "newDeviceVerificationNoticePageOneEmailAccessYes": { - "message": "Ja, ich kann zuverlässig auf meine E-Mails zugreifen" - }, - "turnOnTwoStepLogin": { - "message": "Zwei-Faktor-Authentifizierung aktivieren" - }, - "changeAcctEmail": { - "message": "E-Mail-Adresse des Kontos ändern" - }, "extensionWidth": { "message": "Breite der Erweiterung" }, @@ -5169,6 +5251,12 @@ "changeAtRiskPassword": { "message": "Gefährdetes Passwort ändern" }, + "settingsVaultOptions": { + "message": "Tresoroptionen" + }, + "emptyVaultDescription": { + "message": "Der Tresor schützt mehr als nur deine Passwörter. Speicher hier sicher Zugangsdaten, Identitäten, Karten und Notizen." + }, "introCarouselLabel": { "message": "Willkommen bei Bitwarden" }, @@ -5195,5 +5283,98 @@ }, "secureDevicesBody": { "message": "Speicher eine unbegrenzte Anzahl von Passwörtern auf unbegrenzt vielen Geräten mit Bitwarden-Apps für Smartphones, Browser und Desktop." + }, + "nudgeBadgeAria": { + "message": "1 Benachrichtigung" + }, + "emptyVaultNudgeTitle": { + "message": "Vorhandene Passwörter importieren" + }, + "emptyVaultNudgeBody": { + "message": "Verwende den Importer, um Zugangsdaten schnell zu Bitwarden zu übertragen, ohne sie manuell hinzuzufügen." + }, + "emptyVaultNudgeButton": { + "message": "Jetzt importieren" + }, + "hasItemsVaultNudgeTitle": { + "message": "Willkommen in deinem Tresor!" + }, + "hasItemsVaultNudgeBodyOne": { + "message": "Einträge für die aktuelle Seite automatisch ausfüllen" + }, + "hasItemsVaultNudgeBodyTwo": { + "message": "Favoriten-Einträge für einfachen Zugriff" + }, + "hasItemsVaultNudgeBodyThree": { + "message": "Deinen Tresor nach etwas anderem durchsuchen" + }, + "newLoginNudgeTitle": { + "message": "Spare Zeit mit Auto-Ausfüllen" + }, + "newLoginNudgeBodyOne": { + "message": "Füge eine", + "description": "This is in multiple parts to allow for bold text in the middle of the sentence.", + "example": "Include a Website so this login appears as an autofill suggestion." + }, + "newLoginNudgeBodyBold": { + "message": "Website", + "description": "This is in multiple parts to allow for bold text in the middle of the sentence.", + "example": "Include a Website so this login appears as an autofill suggestion." + }, + "newLoginNudgeBodyTwo": { + "message": "hinzu, damit diese Zugangsdaten als Auto-Ausfüllen-Vorschlag erscheinen.", + "description": "This is in multiple parts to allow for bold text in the middle of the sentence.", + "example": "Include a Website so this login appears as an autofill suggestion." + }, + "newCardNudgeTitle": { + "message": "Nahtlose Online-Kaufabwicklung" + }, + "newCardNudgeBody": { + "message": "Mit Karten kannst du Zahlungsformulare sicher und präzise einfach automatisch ausfüllen." + }, + "newIdentityNudgeTitle": { + "message": "Erstellung von Konten vereinfachen" + }, + "newIdentityNudgeBody": { + "message": "Mit Identitäten kannst du lange Registrierungs- oder Kontaktformulare schnell automatisch ausfüllen." + }, + "newNoteNudgeTitle": { + "message": "Bewahre deine sensiblen Daten sicher auf" + }, + "newNoteNudgeBody": { + "message": "Mit Notizen speicherst du sensible Daten wie Bank- oder Versicherungs-Informationen." + }, + "newSshNudgeTitle": { + "message": "Entwickler-freundlicher SSH-Zugriff" + }, + "newSshNudgeBodyOne": { + "message": "Speicher deine Schlüssel und verbinden dich mit dem SSH-Agenten für eine schnelle und verschlüsselte Authentifizierung.", + "description": "Two part message", + "example": "Store your keys and connect with the SSH agent for fast, encrypted authentication. Learn more about SSH agent" + }, + "newSshNudgeBodyTwo": { + "message": "Erfahre mehr über den SSH-Agenten", + "description": "Two part message", + "example": "Store your keys and connect with the SSH agent for fast, encrypted authentication. Learn more about SSH agent" + }, + "generatorNudgeTitle": { + "message": "Passwörter schnell erstellen" + }, + "generatorNudgeBodyOne": { + "message": "Generiere ganz einfach starke und einzigartige Passwörter, indem du auf den", + "description": "Two part message", + "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." + }, + "generatorNudgeBodyTwo": { + "message": ", um dir zu helfen, deine Zugangsdaten sicher zu halten.", + "description": "Two part message", + "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." + }, + "generatorNudgeBodyAria": { + "message": "Generiere ganz einfach starke und einzigartige Passwörter, indem du auf den \"Passwort generieren\"-Button klickst, um dir zu helfen, deine Zugangsdaten sicher zu halten.", + "description": "Aria label for the body content of the generator nudge" + }, + "noPermissionsViewPage": { + "message": "Du hast keine Berechtigung, diese Seite anzuzeigen. Versuche dich mit einem anderen Konto anzumelden." } } diff --git a/apps/browser/src/_locales/el/messages.json b/apps/browser/src/_locales/el/messages.json index 1c15f70d40c..3fee57b7246 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" + }, "extName": { "message": "Διαχειριστής Κωδικών Πρόσβασης Bitwarden", "description": "Extension name, MUST be less than 40 characters (Safari restriction)" @@ -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", @@ -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": "Επανεκκίνηση εγγραφής" }, @@ -905,7 +911,7 @@ "message": "Όχι" }, "location": { - "message": "Location" + "message": "Τοποθεσία" }, "unexpectedError": { "message": "Παρουσιάστηκε ένα μη αναμενόμενο σφάλμα." @@ -1056,23 +1062,55 @@ "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.", + "notificationNewItemAria": { + "message": "New Item, opens in new window", + "description": "Aria label for the new item button in notification bar confirmation message when error is prompted" + }, + "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": { - "content": "$1" + "label": { + "content": "$1", + "example": "Login" } - }, - "description": "Shown to user after login is updated." + } + }, + "notificationLoginSaveConfirmation": { + "message": "saved to Bitwarden.", + "description": "Shown to user after item is saved." + }, + "notificationLoginUpdatedConfirmation": { + "message": "updated in Bitwarden.", + "description": "Shown to user after item is updated." + }, + "selectItemAriaLabel": { + "message": "Select $ITEMTYPE$, $ITEMNAME$", + "description": "Used by screen readers. $1 is the item type (like vault or folder), $2 is the selected item name.", + "placeholders": { + "itemType": { + "content": "$1" + }, + "itemName": { + "content": "$2" + } + } }, "saveAsNewLoginAction": { "message": "Save as new login", @@ -1082,12 +1120,16 @@ "message": "Update login", "description": "Button text for updating an existing login entry." }, - "saveLoginPrompt": { - "message": "Save login?", + "unlockToSave": { + "message": "Unlock to save this login", + "description": "User prompt to take action in order to save the login they just entered." + }, + "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": { @@ -1323,8 +1365,8 @@ "featureUnavailable": { "message": "Μη διαθέσιμη λειτουργία" }, - "encryptionKeyMigrationRequired": { - "message": "Απαιτείται μεταφορά κλειδιού κρυπτογράφησης. Παρακαλούμε συνδεθείτε μέσω του διαδικτυακού θησαυ/κίου για να ενημερώσετε το κλειδί κρυπτογράφησης." + "legacyEncryptionUnsupported": { + "message": "Legacy encryption is no longer supported. Please contact support to recover your account." }, "premiumMembership": { "message": "Συνδρομή Premium" @@ -1445,7 +1487,7 @@ "message": "Εισάγετε το κλειδί ασφαλείας στη θύρα USB του υπολογιστή σας. Αν έχει κουμπί, πατήστε το." }, "openInNewTab": { - "message": "Open in new tab" + "message": "Άνοιγμα σε νέα καρτέλα" }, "webAuthnAuthenticate": { "message": "Ταυτοποίηση WebAuthn" @@ -1557,6 +1599,24 @@ "autofillSuggestionsSectionTitle": { "message": "Πρόταση αυτόματης συμπλήρωσης" }, + "autofillSpotlightTitle": { + "message": "Easily find autofill suggestions" + }, + "autofillSpotlightDesc": { + "message": "Turn off your browser's autofill settings, so they don't conflict with Bitwarden." + }, + "turnOffBrowserAutofill": { + "message": "Turn off $BROWSER$ autofill", + "placeholders": { + "browser": { + "content": "$1", + "example": "Chrome" + } + } + }, + "turnOffAutofill": { + "message": "Απενεργοποίηση αυτόματης συμπλήρωσης" + }, "showInlineMenuLabel": { "message": "Εμφάνιση μενού αυτόματης συμπλήρωσης στα πεδία της φόρμας" }, @@ -2144,6 +2204,9 @@ "useThisPassword": { "message": "Χρήση αυτού του κωδικού πρόσβασης" }, + "useThisPassphrase": { + "message": "Use this passphrase" + }, "useThisUsername": { "message": "Χρήση αυτού του ονόματος χρήστη" }, @@ -2167,15 +2230,6 @@ "vaultTimeoutAction1": { "message": "Ενέργεια κατά τη λήξη χρονικού ορίου" }, - "newCustomizationOptionsCalloutTitle": { - "message": "New customization options" - }, - "newCustomizationOptionsCalloutContent": { - "message": "Customize your vault experience with quick copy actions, compact mode, and more!" - }, - "newCustomizationOptionsCalloutLink": { - "message": "View all Appearance settings" - }, "lock": { "message": "Κλείδωμα", "description": "Verb form: to make secure or inaccessible by" @@ -2623,6 +2677,10 @@ "message": "Όλα τα Sends", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, + "maxAccessCountReached": { + "message": "Max access count reached", + "description": "This text will be displayed after a Send has been accessed the maximum amount of times." + }, "hideTextByDefault": { "message": "Απόκρυψη κειμένου από προεπιλογή" }, @@ -2963,14 +3021,14 @@ "copyCustomFieldNameNotUnique": { "message": "Δε βρέθηκε μοναδικό αναγνωριστικό." }, - "convertOrganizationEncryptionDesc": { - "message": "$ORGANIZATION$ χρησιμοποιεί SSO με έναν αυτοεξυπηρετητή κλειδιών. Ένας κύριος κωδικός πρόσβασης δεν απαιτείται πλέον για να συνδεθείτε για τα μέλη αυτού του οργανισμού.", - "placeholders": { - "organization": { - "content": "$1", - "example": "My Org Name" - } - } + "removeMasterPasswordForOrganizationUserKeyConnector": { + "message": "A master password is no longer required for members of the following organization. Please confirm the domain below with your organization administrator." + }, + "organizationName": { + "message": "Organization name" + }, + "keyConnectorDomain": { + "message": "Key Connector domain" }, "leaveOrganization": { "message": "Αποχώρηση από τον οργανισμό" @@ -3532,12 +3590,36 @@ "deviceTrusted": { "message": "Αξιόπιστη συσκευή" }, - "sendsNoItemsTitle": { - "message": "Κανένα ενεργό Send", + "trustOrganization": { + "message": "Trust organization" + }, + "trust": { + "message": "Trust" + }, + "doNotTrust": { + "message": "Do not trust" + }, + "organizationNotTrusted": { + "message": "Organization is not trusted" + }, + "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" + }, + "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." + }, + "orgTrustWarning1": { + "message": "This organization has an Enterprise policy that will enroll you in account recovery. Enrollment will allow organization administrators to change your password. Only proceed if you recognize this organization and the fingerprint phrase displayed below matches the organization's fingerprint." + }, + "trustUser": { + "message": "Trust user" + }, + "sendsTitleNoItems": { + "message": "Send sensitive information safely", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, - "sendsNoItemsMessage": { - "message": "Χρήση Send για ασφαλή κοινοποίηση κρυπτογραφημένων πληροφοριών με οποιονδήποτε.", + "sendsBodyNoItems": { + "message": "Share files and data securely with anyone, on any platform. Your information will remain end-to-end encrypted while limiting exposure.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "inputRequired": { @@ -4081,7 +4163,7 @@ "message": "Ενεργός λογαριασμός" }, "bitwardenAccount": { - "message": "Bitwarden account" + "message": "Λογαριασμός Bitwarden" }, "availableAccounts": { "message": "Διαθέσιμοι λογαριασμοί" @@ -4272,7 +4354,7 @@ } }, "viewItemTitleWithField": { - "message": "View item - $ITEMNAME$ - $FIELD$", + "message": "Προβολή στοιχείου - $ITEMNAME$ - $FIELD$", "description": "Title for a link that opens a view for an item.", "placeholders": { "itemname": { @@ -4296,7 +4378,7 @@ } }, "autofillTitleWithField": { - "message": "Autofill - $ITEMNAME$ - $FIELD$", + "message": "Αυτόματη συμπλήρωση - $ITEMNAME$ - $FIELD$", "description": "Title for a button that autofills a login item.", "placeholders": { "itemname": { @@ -4456,6 +4538,33 @@ } } }, + "downloadBitwarden": { + "message": "Λήψη του Bitwarden" + }, + "downloadBitwardenOnAllDevices": { + "message": "Download Bitwarden on all devices" + }, + "getTheMobileApp": { + "message": "Get the mobile app" + }, + "getTheMobileAppDesc": { + "message": "Access your passwords on the go with the Bitwarden mobile app." + }, + "getTheDesktopApp": { + "message": "Get the desktop app" + }, + "getTheDesktopAppDesc": { + "message": "Access your vault without a browser, then set up unlock with biometrics to expedite unlocking in both the desktop app and browser extension." + }, + "downloadFromBitwardenNow": { + "message": "Download from bitwarden.com now" + }, + "getItOnGooglePlay": { + "message": "Get it on Google Play" + }, + "downloadOnTheAppStore": { + "message": "Download on the App Store" + }, "permanentlyDeleteAttachmentConfirmation": { "message": "Είστε σίγουροι ότι θέλετε να διαγράψετε οριστικά αυτό το συνημμένο;" }, @@ -4917,6 +5026,15 @@ "biometricsStatusHelptextUnavailableReasonUnknown": { "message": "Biometric unlock is currently unavailable for an unknown reason." }, + "unlockVault": { + "message": "Unlock your vault in seconds" + }, + "unlockVaultDesc": { + "message": "You can customize your unlock and timeout settings to more quickly access your vault." + }, + "unlockPinSet": { + "message": "Unlock PIN set" + }, "authenticating": { "message": "Ταυτοποίηση" }, @@ -4928,8 +5046,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": { @@ -5079,42 +5197,6 @@ "beta": { "message": "Beta (Δοκιμαστική)" }, - "importantNotice": { - "message": "Σημαντική ειδοποίηση" - }, - "setupTwoStepLogin": { - "message": "Ρύθμιση σύνδεσης δύο βημάτων" - }, - "newDeviceVerificationNoticeContentPage1": { - "message": "Το Bitwarden θα στείλει έναν κωδικό στο ηλ. ταχυδρομείο του λογαριασμού σας για να επαληθεύσει τις συνδέσεις από τις νέες συσκευές που ξεκινούν τον Φεβρουάριο του 2025." - }, - "newDeviceVerificationNoticeContentPage2": { - "message": "Μπορείτε να ορίσετε σύνδεση δύο βημάτων ως εναλλακτικό τρόπο προστασίας του λογαριασμού σας ή να αλλάξετε το ηλ. ταχυδρομείο σας σε ένα που μπορείτε να έχετε πρόσβαση." - }, - "remindMeLater": { - "message": "Υπενθύμιση αργότερα" - }, - "newDeviceVerificationNoticePageOneFormContent": { - "message": "Έχετε αξιόπιστη πρόσβαση στο ηλ. ταχυδρομείο σας, $EMAIL$;", - "placeholders": { - "email": { - "content": "$1", - "example": "your_name@email.com" - } - } - }, - "newDeviceVerificationNoticePageOneEmailAccessNo": { - "message": "Όχι, δεν έχω" - }, - "newDeviceVerificationNoticePageOneEmailAccessYes": { - "message": "Ναι, μπορώ να συνδεθώ αξιόπιστα στο ηλ. ταχυδρομείο μου" - }, - "turnOnTwoStepLogin": { - "message": "Ενεργοποίηση σύνδεσης δύο βημάτων" - }, - "changeAcctEmail": { - "message": "Αλλαγή ηλ. ταχυδρομείου λογαριασμού" - }, "extensionWidth": { "message": "Πλάτος εφαρμογής" }, @@ -5128,10 +5210,10 @@ "message": "The password you entered is incorrect." }, "importSshKey": { - "message": "Import" + "message": "Εισαγωγή" }, "confirmSshKeyPassword": { - "message": "Confirm password" + "message": "Επιβεβαίωση κωδικού πρόσβασης" }, "enterSshKeyPasswordDesc": { "message": "Enter the password for the SSH key." @@ -5169,6 +5251,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 +5283,98 @@ }, "secureDevicesBody": { "message": "Save unlimited passwords across unlimited devices with Bitwarden mobile, browser, and desktop apps." + }, + "nudgeBadgeAria": { + "message": "1 notification" + }, + "emptyVaultNudgeTitle": { + "message": "Import existing passwords" + }, + "emptyVaultNudgeBody": { + "message": "Use the importer to quickly transfer logins to Bitwarden without manually adding them." + }, + "emptyVaultNudgeButton": { + "message": "Εισαγωγή τώρα" + }, + "hasItemsVaultNudgeTitle": { + "message": "Welcome to your vault!" + }, + "hasItemsVaultNudgeBodyOne": { + "message": "Autofill items for the current page" + }, + "hasItemsVaultNudgeBodyTwo": { + "message": "Favorite items for easy access" + }, + "hasItemsVaultNudgeBodyThree": { + "message": "Search your vault for something else" + }, + "newLoginNudgeTitle": { + "message": "Save time with autofill" + }, + "newLoginNudgeBodyOne": { + "message": "Include a", + "description": "This is in multiple parts to allow for bold text in the middle of the sentence.", + "example": "Include a Website so this login appears as an autofill suggestion." + }, + "newLoginNudgeBodyBold": { + "message": "Ιστότοπος", + "description": "This is in multiple parts to allow for bold text in the middle of the sentence.", + "example": "Include a Website so this login appears as an autofill suggestion." + }, + "newLoginNudgeBodyTwo": { + "message": "so this login appears as an autofill suggestion.", + "description": "This is in multiple parts to allow for bold text in the middle of the sentence.", + "example": "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" + }, + "newSshNudgeBodyOne": { + "message": "Store your keys and connect with the SSH agent for fast, encrypted authentication.", + "description": "Two part message", + "example": "Store your keys and connect with the SSH agent for fast, encrypted authentication. Learn more about SSH agent" + }, + "newSshNudgeBodyTwo": { + "message": "Learn more about SSH agent", + "description": "Two part message", + "example": "Store your keys and connect with the SSH agent for fast, encrypted authentication. Learn more about SSH agent" + }, + "generatorNudgeTitle": { + "message": "Quickly create passwords" + }, + "generatorNudgeBodyOne": { + "message": "Easily create strong and unique passwords by clicking on", + "description": "Two part message", + "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." + }, + "generatorNudgeBodyTwo": { + "message": "to help you keep your logins secure.", + "description": "Two part message", + "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." + }, + "generatorNudgeBodyAria": { + "message": "Easily create strong and unique passwords by clicking on the Generate password button to help you keep your logins secure.", + "description": "Aria label for the body content of the generator nudge" + }, + "noPermissionsViewPage": { + "message": "You do not have permissions to view this page. Try logging in with a different account." } } diff --git a/apps/browser/src/_locales/en/messages.json b/apps/browser/src/_locales/en/messages.json index 87b94650b51..3a8c7f14bc0 100644 --- a/apps/browser/src/_locales/en/messages.json +++ b/apps/browser/src/_locales/en/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)" @@ -189,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." }, @@ -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,55 @@ "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" - } + "notificationNewItemAria": { + "message": "New Item, opens in new window", + "description": "Aria label for the new item button in notification bar confirmation message when error is prompted" + }, + "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" + } + } + }, + "notificationLoginSaveConfirmation": { + "message": "saved to Bitwarden.", + "description": "Shown to user after item is saved." + }, + "notificationLoginUpdatedConfirmation": { + "message": "updated in Bitwarden.", + "description": "Shown to user after item is updated." + }, + "selectItemAriaLabel": { + "message": "Select $ITEMTYPE$, $ITEMNAME$", + "description": "Used by screen readers. $1 is the item type (like vault or folder), $2 is the selected item name.", + "placeholders": { + "itemType": { + "content": "$1" }, - "description": "Shown to user after login is updated." + "itemName": { + "content": "$2" + } + } }, "saveAsNewLoginAction": { "message": "Save as new login", @@ -1082,12 +1120,16 @@ "message": "Update login", "description": "Button text for updating an existing login entry." }, - "saveLoginPrompt": { - "message": "Save login?", + "unlockToSave": { + "message": "Unlock to save this login", + "description": "User prompt to take action in order to save the login they just entered." + }, + "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": { @@ -1100,24 +1142,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", @@ -1323,8 +1365,8 @@ "featureUnavailable": { "message": "Feature unavailable" }, - "encryptionKeyMigrationRequired": { - "message": "Encryption key migration required. Please login through the web vault to update your encryption key." + "legacyEncryptionUnsupported": { + "message": "Legacy encryption is no longer supported. Please contact support to recover your account." }, "premiumMembership": { "message": "Premium membership" @@ -1557,6 +1599,24 @@ "autofillSuggestionsSectionTitle": { "message": "Autofill suggestions" }, + "autofillSpotlightTitle": { + "message": "Easily find autofill suggestions" + }, + "autofillSpotlightDesc": { + "message": "Turn off your browser's autofill settings, so they don't conflict with Bitwarden." + }, + "turnOffBrowserAutofill": { + "message": "Turn off $BROWSER$ autofill", + "placeholders": { + "browser": { + "content": "$1", + "example": "Chrome" + } + } + }, + "turnOffAutofill": { + "message": "Turn off autofill" + }, "showInlineMenuLabel": { "message": "Show autofill suggestions on form fields" }, @@ -2093,8 +2153,8 @@ "setYourPinCode": { "message": "Set your PIN code for unlocking Bitwarden. Your PIN settings will be reset if you ever fully log out of the application." }, - "setYourPinCode1": { - "message": "Your PIN will be used to unlock Bitwarden instead of your master password. Your PIN will reset if you ever fully log out of Bitwarden." + "setPinCode": { + "message": "You can use this PIN to unlock Bitwarden. Your PIN will be reset if you ever fully log out of the application." }, "pinRequired": { "message": "PIN code is required." @@ -2144,6 +2204,9 @@ "useThisPassword": { "message": "Use this password" }, + "useThisPassphrase": { + "message": "Use this passphrase" + }, "useThisUsername": { "message": "Use this username" }, @@ -2167,15 +2230,6 @@ "vaultTimeoutAction1": { "message": "Timeout action" }, - "newCustomizationOptionsCalloutTitle": { - "message": "New customization options" - }, - "newCustomizationOptionsCalloutContent": { - "message": "Customize your vault experience with quick copy actions, compact mode, and more!" - }, - "newCustomizationOptionsCalloutLink": { - "message": "View all Appearance settings" - }, "lock": { "message": "Lock", "description": "Verb form: to make secure or inaccessible by" @@ -2461,6 +2515,10 @@ "change": { "message": "Change" }, + "changePassword": { + "message": "Change password", + "description": "Change password button for browser at risk notification on login." + }, "changeButtonTitle": { "message": "Change password - $ITEMNAME$", "placeholders": { @@ -2470,6 +2528,9 @@ } } }, + "atRiskPassword": { + "message": "At-risk password" + }, "atRiskPasswords": { "message": "At-risk passwords" }, @@ -2490,8 +2551,8 @@ "example": "Acme Corp" }, "count": { - "content": "$2", - "example": "2" + "content": "$2", + "example": "2" } } }, @@ -2504,6 +2565,26 @@ } } }, + "atRiskChangePrompt": { + "message": "Your password for this site is at-risk. $ORGANIZATION$ has requested that you change it.", + "placeholders": { + "organization": { + "content": "$1", + "example": "Acme Corp" + } + }, + "description": "Notification body when a login triggers an at-risk password change request and the change password domain is known." + }, + "atRiskNavigatePrompt": { + "message": "$ORGANIZATION$ wants you to change this password because it is at-risk. Navigate to your account settings to change the password.", + "placeholders": { + "organization": { + "content": "$1", + "example": "Acme Corp" + } + }, + "description": "Notification body when a login triggers an at-risk password change request and no change password domain is provided." + }, "reviewAndChangeAtRiskPassword": { "message": "Review and change one at-risk password" }, @@ -2623,6 +2704,10 @@ "message": "All Sends", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, + "maxAccessCountReached": { + "message": "Max access count reached", + "description": "This text will be displayed after a Send has been accessed the maximum amount of times." + }, "hideTextByDefault": { "message": "Hide text by default" }, @@ -2963,14 +3048,14 @@ "copyCustomFieldNameNotUnique": { "message": "No unique identifier found." }, - "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.", - "placeholders": { - "organization": { - "content": "$1", - "example": "My Org Name" - } - } + "removeMasterPasswordForOrganizationUserKeyConnector": { + "message": "A master password is no longer required for members of the following organization. Please confirm the domain below with your organization administrator." + }, + "organizationName": { + "message": "Organization name" + }, + "keyConnectorDomain": { + "message": "Key Connector domain" }, "leaveOrganization": { "message": "Leave organization" @@ -3532,12 +3617,36 @@ "deviceTrusted": { "message": "Device trusted" }, - "sendsNoItemsTitle": { - "message": "No active Sends", + "trustOrganization": { + "message": "Trust organization" + }, + "trust": { + "message": "Trust" + }, + "doNotTrust": { + "message": "Do not trust" + }, + "organizationNotTrusted": { + "message": "Organization is not trusted" + }, + "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" + }, + "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." + }, + "orgTrustWarning1": { + "message": "This organization has an Enterprise policy that will enroll you in account recovery. Enrollment will allow organization administrators to change your password. Only proceed if you recognize this organization and the fingerprint phrase displayed below matches the organization's fingerprint." + }, + "trustUser": { + "message": "Trust user" + }, + "sendsTitleNoItems": { + "message": "Send sensitive information safely", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, - "sendsNoItemsMessage": { - "message": "Use Send to securely share encrypted information with anyone.", + "sendsBodyNoItems": { + "message": "Share files and data securely with anyone, on any platform. Your information will remain end-to-end encrypted while limiting exposure.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "inputRequired": { @@ -4456,6 +4565,33 @@ } } }, + "downloadBitwarden": { + "message": "Download Bitwarden" + }, + "downloadBitwardenOnAllDevices": { + "message": "Download Bitwarden on all devices" + }, + "getTheMobileApp": { + "message": "Get the mobile app" + }, + "getTheMobileAppDesc": { + "message": "Access your passwords on the go with the Bitwarden mobile app." + }, + "getTheDesktopApp": { + "message": "Get the desktop app" + }, + "getTheDesktopAppDesc": { + "message": "Access your vault without a browser, then set up unlock with biometrics to expedite unlocking in both the desktop app and browser extension." + }, + "downloadFromBitwardenNow": { + "message": "Download from bitwarden.com now" + }, + "getItOnGooglePlay": { + "message": "Get it on Google Play" + }, + "downloadOnTheAppStore": { + "message": "Download on the App Store" + }, "permanentlyDeleteAttachmentConfirmation": { "message": "Are you sure you want to permanently delete this attachment?" }, @@ -4917,6 +5053,15 @@ "biometricsStatusHelptextUnavailableReasonUnknown": { "message": "Biometric unlock is currently unavailable for an unknown reason." }, + "unlockVault": { + "message": "Unlock your vault in seconds" + }, + "unlockVaultDesc": { + "message": "You can customize your unlock and timeout settings to more quickly access your vault." + }, + "unlockPinSet": { + "message": "Unlock PIN set" + }, "authenticating": { "message": "Authenticating" }, @@ -5079,42 +5224,6 @@ "beta": { "message": "Beta" }, - "importantNotice": { - "message": "Important notice" - }, - "setupTwoStepLogin": { - "message": "Set up two-step login" - }, - "newDeviceVerificationNoticeContentPage1": { - "message": "Bitwarden will send a code to your account email to verify logins from new devices starting in February 2025." - }, - "newDeviceVerificationNoticeContentPage2": { - "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" - }, - "newDeviceVerificationNoticePageOneFormContent": { - "message": "Do you have reliable access to your email, $EMAIL$?", - "placeholders": { - "email": { - "content": "$1", - "example": "your_name@email.com" - } - } - }, - "newDeviceVerificationNoticePageOneEmailAccessNo": { - "message": "No, I do not" - }, - "newDeviceVerificationNoticePageOneEmailAccessYes": { - "message": "Yes, I can reliably access my email" - }, - "turnOnTwoStepLogin": { - "message": "Turn on two-step login" - }, - "changeAcctEmail": { - "message": "Change account email" - }, "extensionWidth": { "message": "Extension width" }, @@ -5169,6 +5278,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 +5310,98 @@ }, "secureDevicesBody": { "message": "Save unlimited passwords across unlimited devices with Bitwarden mobile, browser, and desktop apps." + }, + "nudgeBadgeAria": { + "message": "1 notification" + }, + "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!" + }, + "hasItemsVaultNudgeBodyOne": { + "message": "Autofill items for the current page" + }, + "hasItemsVaultNudgeBodyTwo": { + "message": "Favorite items for easy access" + }, + "hasItemsVaultNudgeBodyThree": { + "message": "Search your vault for something else" + }, + "newLoginNudgeTitle": { + "message": "Save time with autofill" + }, + "newLoginNudgeBodyOne": { + "message": "Include a", + "description": "This is in multiple parts to allow for bold text in the middle of the sentence.", + "example": "Include a Website so this login appears as an autofill suggestion." + }, + "newLoginNudgeBodyBold": { + "message": "Website", + "description": "This is in multiple parts to allow for bold text in the middle of the sentence.", + "example": "Include a Website so this login appears as an autofill suggestion." + }, + "newLoginNudgeBodyTwo": { + "message": "so this login appears as an autofill suggestion.", + "description": "This is in multiple parts to allow for bold text in the middle of the sentence.", + "example": "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" + }, + "newSshNudgeBodyOne": { + "message": "Store your keys and connect with the SSH agent for fast, encrypted authentication.", + "description": "Two part message", + "example": "Store your keys and connect with the SSH agent for fast, encrypted authentication. Learn more about SSH agent" + }, + "newSshNudgeBodyTwo": { + "message": "Learn more about SSH agent", + "description": "Two part message", + "example": "Store your keys and connect with the SSH agent for fast, encrypted authentication. Learn more about SSH agent" + }, + "generatorNudgeTitle": { + "message": "Quickly create passwords" + }, + "generatorNudgeBodyOne": { + "message": "Easily create strong and unique passwords by clicking on", + "description": "Two part message", + "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." + }, + "generatorNudgeBodyTwo": { + "message": "to help you keep your logins secure.", + "description": "Two part message", + "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." + }, + "generatorNudgeBodyAria": { + "message": "Easily create strong and unique passwords by clicking on the Generate password button to help you keep your logins secure.", + "description": "Aria label for the body content of the generator nudge" + }, + "noPermissionsViewPage": { + "message": "You do not have permissions to view this page. Try logging in with a different account." } } diff --git a/apps/browser/src/_locales/en_GB/messages.json b/apps/browser/src/_locales/en_GB/messages.json index 520af1ca8ff..635a416e002 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,55 @@ "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.", + "notificationNewItemAria": { + "message": "New Item, opens in new window", + "description": "Aria label for the new item button in notification bar confirmation message when error is prompted" + }, + "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": { - "content": "$1" + "label": { + "content": "$1", + "example": "Login" } - }, - "description": "Shown to user after login is updated." + } + }, + "notificationLoginSaveConfirmation": { + "message": "saved to Bitwarden.", + "description": "Shown to user after item is saved." + }, + "notificationLoginUpdatedConfirmation": { + "message": "updated in Bitwarden.", + "description": "Shown to user after item is updated." + }, + "selectItemAriaLabel": { + "message": "Select $ITEMTYPE$, $ITEMNAME$", + "description": "Used by screen readers. $1 is the item type (like vault or folder), $2 is the selected item name.", + "placeholders": { + "itemType": { + "content": "$1" + }, + "itemName": { + "content": "$2" + } + } }, "saveAsNewLoginAction": { "message": "Save as new login", @@ -1082,12 +1120,16 @@ "message": "Update login", "description": "Button text for updating an existing login entry." }, - "saveLoginPrompt": { - "message": "Save login?", + "unlockToSave": { + "message": "Unlock to save this login", + "description": "User prompt to take action in order to save the login they just entered." + }, + "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": { @@ -1323,8 +1365,8 @@ "featureUnavailable": { "message": "Feature unavailable" }, - "encryptionKeyMigrationRequired": { - "message": "Encryption key migration required. Please login through the web vault to update your encryption key." + "legacyEncryptionUnsupported": { + "message": "Legacy encryption is no longer supported. Please contact support to recover your account." }, "premiumMembership": { "message": "Premium membership" @@ -1557,6 +1599,24 @@ "autofillSuggestionsSectionTitle": { "message": "Autofill suggestions" }, + "autofillSpotlightTitle": { + "message": "Easily find autofill suggestions" + }, + "autofillSpotlightDesc": { + "message": "Turn off your browser's autofill settings, so they don't conflict with Bitwarden." + }, + "turnOffBrowserAutofill": { + "message": "Turn off $BROWSER$ autofill", + "placeholders": { + "browser": { + "content": "$1", + "example": "Chrome" + } + } + }, + "turnOffAutofill": { + "message": "Turn off autofill" + }, "showInlineMenuLabel": { "message": "Show autofill suggestions on form fields" }, @@ -2144,6 +2204,9 @@ "useThisPassword": { "message": "Use this password" }, + "useThisPassphrase": { + "message": "Use this passphrase" + }, "useThisUsername": { "message": "Use this username" }, @@ -2167,15 +2230,6 @@ "vaultTimeoutAction1": { "message": "Timeout action" }, - "newCustomizationOptionsCalloutTitle": { - "message": "New customisation options" - }, - "newCustomizationOptionsCalloutContent": { - "message": "Customise your vault experience with quick copy actions, compact mode, and more!" - }, - "newCustomizationOptionsCalloutLink": { - "message": "View all Appearance settings" - }, "lock": { "message": "Lock", "description": "Verb form: to make secure or inaccessible by" @@ -2623,6 +2677,10 @@ "message": "All Sends", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, + "maxAccessCountReached": { + "message": "Max access count reached", + "description": "This text will be displayed after a Send has been accessed the maximum amount of times." + }, "hideTextByDefault": { "message": "Hide text by default" }, @@ -2963,14 +3021,14 @@ "copyCustomFieldNameNotUnique": { "message": "No unique identifier found." }, - "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 organisation.", - "placeholders": { - "organization": { - "content": "$1", - "example": "My Org Name" - } - } + "removeMasterPasswordForOrganizationUserKeyConnector": { + "message": "A master password is no longer required for members of the following organisation. Please confirm the domain below with your organisation administrator." + }, + "organizationName": { + "message": "Organisation name" + }, + "keyConnectorDomain": { + "message": "Key Connector domain" }, "leaveOrganization": { "message": "Leave organisation" @@ -3532,12 +3590,36 @@ "deviceTrusted": { "message": "Device trusted" }, - "sendsNoItemsTitle": { - "message": "No active Sends", + "trustOrganization": { + "message": "Trust organisation" + }, + "trust": { + "message": "Trust" + }, + "doNotTrust": { + "message": "Do not trust" + }, + "organizationNotTrusted": { + "message": "Organisation is not trusted" + }, + "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" + }, + "orgTrustWarning": { + "message": "For the security of your account, only proceed if you are a member of this organisation, have account recovery enabled, and the fingerprint displayed below matches the organisation's fingerprint." + }, + "orgTrustWarning1": { + "message": "This organisation has an Enterprise policy that will enrol you in account recovery. Enrolment will allow organisation administrators to change your password. Only proceed if you recognise this organisation and the fingerprint phrase displayed below matches the organisation's fingerprint." + }, + "trustUser": { + "message": "Trust user" + }, + "sendsTitleNoItems": { + "message": "Send sensitive information safely", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, - "sendsNoItemsMessage": { - "message": "Use Send to securely share encrypted information with anyone.", + "sendsBodyNoItems": { + "message": "Share files and data securely with anyone, on any platform. Your information will remain end-to-end encrypted while limiting exposure.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "inputRequired": { @@ -4456,6 +4538,33 @@ } } }, + "downloadBitwarden": { + "message": "Download Bitwarden" + }, + "downloadBitwardenOnAllDevices": { + "message": "Download Bitwarden on all devices" + }, + "getTheMobileApp": { + "message": "Get the mobile app" + }, + "getTheMobileAppDesc": { + "message": "Access your passwords on the go with the Bitwarden mobile app." + }, + "getTheDesktopApp": { + "message": "Get the desktop app" + }, + "getTheDesktopAppDesc": { + "message": "Access your vault without a browser, then set up unlock with biometrics to expedite unlocking in both the desktop app and browser extension." + }, + "downloadFromBitwardenNow": { + "message": "Download from bitwarden.com now" + }, + "getItOnGooglePlay": { + "message": "Get it on Google Play" + }, + "downloadOnTheAppStore": { + "message": "Download on the App Store" + }, "permanentlyDeleteAttachmentConfirmation": { "message": "Are you sure you want to permanently delete this attachment?" }, @@ -4917,6 +5026,15 @@ "biometricsStatusHelptextUnavailableReasonUnknown": { "message": "Biometric unlock is currently unavailable for an unknown reason." }, + "unlockVault": { + "message": "Unlock your vault in seconds" + }, + "unlockVaultDesc": { + "message": "You can customise your unlock and timeout settings to more quickly access your vault." + }, + "unlockPinSet": { + "message": "Unlock PIN set" + }, "authenticating": { "message": "Authenticating" }, @@ -4928,8 +5046,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": { @@ -5079,42 +5197,6 @@ "beta": { "message": "Beta" }, - "importantNotice": { - "message": "Important notice" - }, - "setupTwoStepLogin": { - "message": "Set up two-step login" - }, - "newDeviceVerificationNoticeContentPage1": { - "message": "Bitwarden will send a code to your account email to verify logins from new devices starting in February 2025." - }, - "newDeviceVerificationNoticeContentPage2": { - "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" - }, - "newDeviceVerificationNoticePageOneFormContent": { - "message": "Do you have reliable access to your email, $EMAIL$?", - "placeholders": { - "email": { - "content": "$1", - "example": "your_name@email.com" - } - } - }, - "newDeviceVerificationNoticePageOneEmailAccessNo": { - "message": "No, I do not" - }, - "newDeviceVerificationNoticePageOneEmailAccessYes": { - "message": "Yes, I can reliably access my email" - }, - "turnOnTwoStepLogin": { - "message": "Turn on two-step login" - }, - "changeAcctEmail": { - "message": "Change account email" - }, "extensionWidth": { "message": "Extension width" }, @@ -5169,6 +5251,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 +5283,98 @@ }, "secureDevicesBody": { "message": "Save unlimited passwords across unlimited devices with Bitwarden mobile, browser, and desktop apps." + }, + "nudgeBadgeAria": { + "message": "1 notification" + }, + "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!" + }, + "hasItemsVaultNudgeBodyOne": { + "message": "Autofill items for the current page" + }, + "hasItemsVaultNudgeBodyTwo": { + "message": "Favourite items for easy access" + }, + "hasItemsVaultNudgeBodyThree": { + "message": "Search your vault for something else" + }, + "newLoginNudgeTitle": { + "message": "Save time with autofill" + }, + "newLoginNudgeBodyOne": { + "message": "Include a", + "description": "This is in multiple parts to allow for bold text in the middle of the sentence.", + "example": "Include a Website so this login appears as an autofill suggestion." + }, + "newLoginNudgeBodyBold": { + "message": "Website", + "description": "This is in multiple parts to allow for bold text in the middle of the sentence.", + "example": "Include a Website so this login appears as an autofill suggestion." + }, + "newLoginNudgeBodyTwo": { + "message": "so this login appears as an autofill suggestion.", + "description": "This is in multiple parts to allow for bold text in the middle of the sentence.", + "example": "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" + }, + "newSshNudgeBodyOne": { + "message": "Store your keys and connect with the SSH agent for fast, encrypted authentication.", + "description": "Two part message", + "example": "Store your keys and connect with the SSH agent for fast, encrypted authentication. Learn more about SSH agent" + }, + "newSshNudgeBodyTwo": { + "message": "Learn more about SSH agent", + "description": "Two part message", + "example": "Store your keys and connect with the SSH agent for fast, encrypted authentication. Learn more about SSH agent" + }, + "generatorNudgeTitle": { + "message": "Quickly create passwords" + }, + "generatorNudgeBodyOne": { + "message": "Easily create strong and unique passwords by clicking on", + "description": "Two part message", + "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." + }, + "generatorNudgeBodyTwo": { + "message": "to help you keep your logins secure.", + "description": "Two part message", + "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." + }, + "generatorNudgeBodyAria": { + "message": "Easily create strong and unique passwords by clicking on the Generate password button to help you keep your logins secure.", + "description": "Aria label for the body content of the generator nudge" + }, + "noPermissionsViewPage": { + "message": "You do not have permissions to view this page. Try logging in with a different account." } } diff --git a/apps/browser/src/_locales/en_IN/messages.json b/apps/browser/src/_locales/en_IN/messages.json index f75664f10db..1baf1d63257 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,55 @@ "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.", + "notificationNewItemAria": { + "message": "New Item, opens in new window", + "description": "Aria label for the new item button in notification bar confirmation message when error is prompted" + }, + "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": { - "content": "$1" + "label": { + "content": "$1", + "example": "Login" } - }, - "description": "Shown to user after login is updated." + } + }, + "notificationLoginSaveConfirmation": { + "message": "saved to Bitwarden.", + "description": "Shown to user after item is saved." + }, + "notificationLoginUpdatedConfirmation": { + "message": "updated in Bitwarden.", + "description": "Shown to user after item is updated." + }, + "selectItemAriaLabel": { + "message": "Select $ITEMTYPE$, $ITEMNAME$", + "description": "Used by screen readers. $1 is the item type (like vault or folder), $2 is the selected item name.", + "placeholders": { + "itemType": { + "content": "$1" + }, + "itemName": { + "content": "$2" + } + } }, "saveAsNewLoginAction": { "message": "Save as new login", @@ -1082,12 +1120,16 @@ "message": "Update login", "description": "Button text for updating an existing login entry." }, - "saveLoginPrompt": { - "message": "Save login?", + "unlockToSave": { + "message": "Unlock to save this login", + "description": "User prompt to take action in order to save the login they just entered." + }, + "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": { @@ -1323,8 +1365,8 @@ "featureUnavailable": { "message": "Feature unavailable" }, - "encryptionKeyMigrationRequired": { - "message": "Encryption key migration required. Please login through the web vault to update your encryption key." + "legacyEncryptionUnsupported": { + "message": "Legacy encryption is no longer supported. Please contact support to recover your account." }, "premiumMembership": { "message": "Premium membership" @@ -1557,6 +1599,24 @@ "autofillSuggestionsSectionTitle": { "message": "Autofill suggestions" }, + "autofillSpotlightTitle": { + "message": "Easily find autofill suggestions" + }, + "autofillSpotlightDesc": { + "message": "Turn off your browser's autofill settings, so they don't conflict with Bitwarden." + }, + "turnOffBrowserAutofill": { + "message": "Turn off $BROWSER$ autofill", + "placeholders": { + "browser": { + "content": "$1", + "example": "Chrome" + } + } + }, + "turnOffAutofill": { + "message": "Turn off autofill" + }, "showInlineMenuLabel": { "message": "Show autofill suggestions on form fields" }, @@ -2144,6 +2204,9 @@ "useThisPassword": { "message": "Use this password" }, + "useThisPassphrase": { + "message": "Use this passphrase" + }, "useThisUsername": { "message": "Use this username" }, @@ -2167,15 +2230,6 @@ "vaultTimeoutAction1": { "message": "Timeout action" }, - "newCustomizationOptionsCalloutTitle": { - "message": "New customisation options" - }, - "newCustomizationOptionsCalloutContent": { - "message": "Customise your vault experience with quick copy actions, compact mode, and more!" - }, - "newCustomizationOptionsCalloutLink": { - "message": "View all Appearance settings" - }, "lock": { "message": "Lock", "description": "Verb form: to make secure or inaccessible by" @@ -2623,6 +2677,10 @@ "message": "All Sends", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, + "maxAccessCountReached": { + "message": "Max access count reached", + "description": "This text will be displayed after a Send has been accessed the maximum amount of times." + }, "hideTextByDefault": { "message": "Hide text by default" }, @@ -2963,14 +3021,14 @@ "copyCustomFieldNameNotUnique": { "message": "No unique identifier found." }, - "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.", - "placeholders": { - "organization": { - "content": "$1", - "example": "My Org Name" - } - } + "removeMasterPasswordForOrganizationUserKeyConnector": { + "message": "A master password is no longer required for members of the following organisation. Please confirm the domain below with your organisation administrator." + }, + "organizationName": { + "message": "Organisation name" + }, + "keyConnectorDomain": { + "message": "Key Connector domain" }, "leaveOrganization": { "message": "Leave Organization" @@ -3532,12 +3590,36 @@ "deviceTrusted": { "message": "Device trusted" }, - "sendsNoItemsTitle": { - "message": "No active Sends", + "trustOrganization": { + "message": "Trust organisation" + }, + "trust": { + "message": "Trust" + }, + "doNotTrust": { + "message": "Do not trust" + }, + "organizationNotTrusted": { + "message": "Organisation is not trusted" + }, + "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" + }, + "orgTrustWarning": { + "message": "For the security of your account, only proceed if you are a member of this organisation, have account recovery enabled, and the fingerprint displayed below matches the organisation's fingerprint." + }, + "orgTrustWarning1": { + "message": "This organisation has an Enterprise policy that will enrol you in account recovery. Enrolment will allow organisation administrators to change your password. Only proceed if you recognise this organisation and the fingerprint phrase displayed below matches the organisation's fingerprint." + }, + "trustUser": { + "message": "Trust user" + }, + "sendsTitleNoItems": { + "message": "Send sensitive information safely", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, - "sendsNoItemsMessage": { - "message": "Use Send to securely share encrypted information with anyone.", + "sendsBodyNoItems": { + "message": "Share files and data securely with anyone, on any platform. Your information will remain end-to-end encrypted while limiting exposure.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "inputRequired": { @@ -4456,6 +4538,33 @@ } } }, + "downloadBitwarden": { + "message": "Download Bitwarden" + }, + "downloadBitwardenOnAllDevices": { + "message": "Download Bitwarden on all devices" + }, + "getTheMobileApp": { + "message": "Get the mobile app" + }, + "getTheMobileAppDesc": { + "message": "Access your passwords on the go with the Bitwarden mobile app." + }, + "getTheDesktopApp": { + "message": "Get the desktop app" + }, + "getTheDesktopAppDesc": { + "message": "Access your vault without a browser, then set up unlock with biometrics to expedite unlocking in both the desktop app and browser extension." + }, + "downloadFromBitwardenNow": { + "message": "Download from bitwarden.com now" + }, + "getItOnGooglePlay": { + "message": "Get it on Google Play" + }, + "downloadOnTheAppStore": { + "message": "Download on the App Store" + }, "permanentlyDeleteAttachmentConfirmation": { "message": "Are you sure you want to permanently delete this attachment?" }, @@ -4917,6 +5026,15 @@ "biometricsStatusHelptextUnavailableReasonUnknown": { "message": "Biometric unlock is currently unavailable for an unknown reason." }, + "unlockVault": { + "message": "Unlock your vault in seconds" + }, + "unlockVaultDesc": { + "message": "You can customise your unlock and timeout settings to more quickly access your vault." + }, + "unlockPinSet": { + "message": "Unlock PIN set" + }, "authenticating": { "message": "Authenticating" }, @@ -4928,8 +5046,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": { @@ -5079,42 +5197,6 @@ "beta": { "message": "Beta" }, - "importantNotice": { - "message": "Important notice" - }, - "setupTwoStepLogin": { - "message": "Set up two-step login" - }, - "newDeviceVerificationNoticeContentPage1": { - "message": "Bitwarden will send a code to your account email to verify logins from new devices starting in February 2025." - }, - "newDeviceVerificationNoticeContentPage2": { - "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" - }, - "newDeviceVerificationNoticePageOneFormContent": { - "message": "Do you have reliable access to your email, $EMAIL$?", - "placeholders": { - "email": { - "content": "$1", - "example": "your_name@email.com" - } - } - }, - "newDeviceVerificationNoticePageOneEmailAccessNo": { - "message": "No, I do not" - }, - "newDeviceVerificationNoticePageOneEmailAccessYes": { - "message": "Yes, I can reliably access my email" - }, - "turnOnTwoStepLogin": { - "message": "Turn on two-step login" - }, - "changeAcctEmail": { - "message": "Change account email" - }, "extensionWidth": { "message": "Extension width" }, @@ -5169,6 +5251,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 +5283,98 @@ }, "secureDevicesBody": { "message": "Save unlimited passwords across unlimited devices with Bitwarden mobile, browser, and desktop apps." + }, + "nudgeBadgeAria": { + "message": "1 notification" + }, + "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!" + }, + "hasItemsVaultNudgeBodyOne": { + "message": "Autofill items for the current page" + }, + "hasItemsVaultNudgeBodyTwo": { + "message": "Favourite items for easy access" + }, + "hasItemsVaultNudgeBodyThree": { + "message": "Search your vault for something else" + }, + "newLoginNudgeTitle": { + "message": "Save time with autofill" + }, + "newLoginNudgeBodyOne": { + "message": "Include a", + "description": "This is in multiple parts to allow for bold text in the middle of the sentence.", + "example": "Include a Website so this login appears as an autofill suggestion." + }, + "newLoginNudgeBodyBold": { + "message": "Website", + "description": "This is in multiple parts to allow for bold text in the middle of the sentence.", + "example": "Include a Website so this login appears as an autofill suggestion." + }, + "newLoginNudgeBodyTwo": { + "message": "so this login appears as an autofill suggestion.", + "description": "This is in multiple parts to allow for bold text in the middle of the sentence.", + "example": "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" + }, + "newSshNudgeBodyOne": { + "message": "Store your keys and connect with the SSH agent for fast, encrypted authentication.", + "description": "Two part message", + "example": "Store your keys and connect with the SSH agent for fast, encrypted authentication. Learn more about SSH agent" + }, + "newSshNudgeBodyTwo": { + "message": "Learn more about SSH agent", + "description": "Two part message", + "example": "Store your keys and connect with the SSH agent for fast, encrypted authentication. Learn more about SSH agent" + }, + "generatorNudgeTitle": { + "message": "Quickly create passwords" + }, + "generatorNudgeBodyOne": { + "message": "Easily create strong and unique passwords by clicking on", + "description": "Two part message", + "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." + }, + "generatorNudgeBodyTwo": { + "message": "to help you keep your logins secure.", + "description": "Two part message", + "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." + }, + "generatorNudgeBodyAria": { + "message": "Easily create strong and unique passwords by clicking on the Generate password button to help you keep your logins secure.", + "description": "Aria label for the body content of the generator nudge" + }, + "noPermissionsViewPage": { + "message": "You do not have permissions to view this page. Try logging in with a different account." } } diff --git a/apps/browser/src/_locales/es/messages.json b/apps/browser/src/_locales/es/messages.json index 1400cb78845..090bb8db08e 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": "Logo de Bitwarden" + }, "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", @@ -653,7 +656,7 @@ "message": "Tu navegador web no soporta copiar al portapapeles facilmente. Cópialo manualmente." }, "verifyYourIdentity": { - "message": "Verify your identity" + "message": "Verifica tu identidad" }, "weDontRecognizeThisDevice": { "message": "No reconocemos este dispositivo. Introduce el código enviado a tu correo electrónico para verificar tu identidad." @@ -869,19 +872,22 @@ "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 identificarte" }, "duoTwoFactorRequiredPageSubtitle": { - "message": "Duo two-step login is required for your account. Follow the steps below to finish logging in." + "message": "Se requiere inicio de sesión en dos pasos para tu cuenta. Sigue los pasos siguientes para terminar de iniciar sesión." }, "followTheStepsBelowToFinishLoggingIn": { - "message": "Follow the steps below to finish logging in." + "message": "Sigue los pasos de abajo para terminar de iniciar sesión." + }, + "followTheStepsBelowToFinishLoggingInWithSecurityKey": { + "message": "Follow the steps below to finish logging in with your security key." }, "restartRegistration": { "message": "Reiniciar registro" @@ -905,7 +911,7 @@ "message": "No" }, "location": { - "message": "Location" + "message": "Ubicación" }, "unexpectedError": { "message": "Ha ocurrido un error inesperado." @@ -1056,23 +1062,55 @@ "notificationAddSave": { "message": "Guardar" }, - "loginSaveSuccessDetails": { - "message": "$USERNAME$ saved to Bitwarden.", + "notificationViewAria": { + "message": "Ver $ITEMNAME$, se abre en una nueva ventana", "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.", + "notificationNewItemAria": { + "message": "Nuevo Elemento, se abre en una nueva ventana", + "description": "Aria label for the new item button in notification bar confirmation message when error is prompted" + }, + "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": { - "content": "$1" + "label": { + "content": "$1", + "example": "Login" } - }, - "description": "Shown to user after login is updated." + } + }, + "notificationLoginSaveConfirmation": { + "message": "saved to Bitwarden.", + "description": "Shown to user after item is saved." + }, + "notificationLoginUpdatedConfirmation": { + "message": "updated in Bitwarden.", + "description": "Shown to user after item is updated." + }, + "selectItemAriaLabel": { + "message": "Select $ITEMTYPE$, $ITEMNAME$", + "description": "Used by screen readers. $1 is the item type (like vault or folder), $2 is the selected item name.", + "placeholders": { + "itemType": { + "content": "$1" + }, + "itemName": { + "content": "$2" + } + } }, "saveAsNewLoginAction": { "message": "Save as new login", @@ -1082,12 +1120,16 @@ "message": "Update login", "description": "Button text for updating an existing login entry." }, - "saveLoginPrompt": { - "message": "Save login?", + "unlockToSave": { + "message": "Unlock to save this login", + "description": "User prompt to take action in order to save the login they just entered." + }, + "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 +1137,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 +1166,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": { @@ -1323,8 +1365,8 @@ "featureUnavailable": { "message": "Característica no disponible" }, - "encryptionKeyMigrationRequired": { - "message": "Se requiere migración de la clave de cifrado. Por favor, inicie sesión a través de la caja fuerte para actualizar su clave de cifrado." + "legacyEncryptionUnsupported": { + "message": "Legacy encryption is no longer supported. Please contact support to recover your account." }, "premiumMembership": { "message": "Membresía Premium" @@ -1445,7 +1487,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" @@ -1557,6 +1599,24 @@ "autofillSuggestionsSectionTitle": { "message": "Sugerencias de autocompletar" }, + "autofillSpotlightTitle": { + "message": "Easily find autofill suggestions" + }, + "autofillSpotlightDesc": { + "message": "Turn off your browser's autofill settings, so they don't conflict with Bitwarden." + }, + "turnOffBrowserAutofill": { + "message": "Turn off $BROWSER$ autofill", + "placeholders": { + "browser": { + "content": "$1", + "example": "Chrome" + } + } + }, + "turnOffAutofill": { + "message": "Turn off autofill" + }, "showInlineMenuLabel": { "message": "Show autofill suggestions on form fields" }, @@ -1663,7 +1723,7 @@ "message": "Arrastrar para ordenar" }, "dragToReorder": { - "message": "Drag to reorder" + "message": "Arrastra para reordenar" }, "cfTypeText": { "message": "Texto" @@ -2144,6 +2204,9 @@ "useThisPassword": { "message": "Usar esta contraseña" }, + "useThisPassphrase": { + "message": "Use this passphrase" + }, "useThisUsername": { "message": "Usar este nombre de usuario" }, @@ -2167,15 +2230,6 @@ "vaultTimeoutAction1": { "message": "Acción de tiempo agotado" }, - "newCustomizationOptionsCalloutTitle": { - "message": "New customization options" - }, - "newCustomizationOptionsCalloutContent": { - "message": "Customize your vault experience with quick copy actions, compact mode, and more!" - }, - "newCustomizationOptionsCalloutLink": { - "message": "View all Appearance settings" - }, "lock": { "message": "Bloquear", "description": "Verb form: to make secure or inaccessible by" @@ -2526,10 +2580,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": { @@ -2543,7 +2597,7 @@ "message": "Illustration of the Bitwarden autofill menu displaying a generated password." }, "updateInBitwarden": { - "message": "Update in Bitwarden" + "message": "Actualizar en Bitwarden" }, "updateInBitwardenSlideDesc": { "message": "Bitwarden will then prompt you to update the password in the password manager.", @@ -2623,6 +2677,10 @@ "message": "Todos los Sends", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, + "maxAccessCountReached": { + "message": "Max access count reached", + "description": "This text will be displayed after a Send has been accessed the maximum amount of times." + }, "hideTextByDefault": { "message": "Hide text by default" }, @@ -2963,14 +3021,14 @@ "copyCustomFieldNameNotUnique": { "message": "Identificador único no encontrado." }, - "convertOrganizationEncryptionDesc": { - "message": "$ORGANIZATION$ está usando SSO con un servidor de claves autoalojado. Los miembros de esta organización ya no necesitarán una contraseña maestra para iniciar sesión.", - "placeholders": { - "organization": { - "content": "$1", - "example": "My Org Name" - } - } + "removeMasterPasswordForOrganizationUserKeyConnector": { + "message": "Ya no es necesaria una contraseña maestra para los miembros de la siguiente organización. Confirma el dominio que aparece a continuación con el administrador de tu organización." + }, + "organizationName": { + "message": "Nombre de la organización" + }, + "keyConnectorDomain": { + "message": "Dominio del conector de clave" }, "leaveOrganization": { "message": "Abandonar organización" @@ -3355,7 +3413,7 @@ "message": "Inicio de sesión en proceso" }, "logInRequestSent": { - "message": "Request sent" + "message": "Solicitud enviada" }, "exposedMasterPassword": { "message": "Contraseña maestra comprometida" @@ -3532,12 +3590,36 @@ "deviceTrusted": { "message": "Dispositivo de confianza" }, - "sendsNoItemsTitle": { - "message": "No active Sends", + "trustOrganization": { + "message": "Trust organization" + }, + "trust": { + "message": "Trust" + }, + "doNotTrust": { + "message": "Do not trust" + }, + "organizationNotTrusted": { + "message": "Organization is not trusted" + }, + "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" + }, + "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." + }, + "orgTrustWarning1": { + "message": "This organization has an Enterprise policy that will enroll you in account recovery. Enrollment will allow organization administrators to change your password. Only proceed if you recognize this organization and the fingerprint phrase displayed below matches the organization's fingerprint." + }, + "trustUser": { + "message": "Trust user" + }, + "sendsTitleNoItems": { + "message": "Send sensitive information safely", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, - "sendsNoItemsMessage": { - "message": "Use Send to securely share encrypted information with anyone.", + "sendsBodyNoItems": { + "message": "Share files and data securely with anyone, on any platform. Your information will remain end-to-end encrypted while limiting exposure.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "inputRequired": { @@ -3614,10 +3696,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", @@ -3742,7 +3824,7 @@ "description": "Screen reader text (aria-label) for new login button within inline menu" }, "newCard": { - "message": "New card", + "message": "Nueva tarjeta", "description": "Button text to display within inline menu when there are no matching items on a credit card field" }, "addNewCardItemAria": { @@ -4081,7 +4163,7 @@ "message": "Cuenta activa" }, "bitwardenAccount": { - "message": "Bitwarden account" + "message": "Cuenta de Bitwarden" }, "availableAccounts": { "message": "Cuentas disponibles" @@ -4179,7 +4261,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": { @@ -4456,6 +4538,33 @@ } } }, + "downloadBitwarden": { + "message": "Download Bitwarden" + }, + "downloadBitwardenOnAllDevices": { + "message": "Download Bitwarden on all devices" + }, + "getTheMobileApp": { + "message": "Get the mobile app" + }, + "getTheMobileAppDesc": { + "message": "Access your passwords on the go with the Bitwarden mobile app." + }, + "getTheDesktopApp": { + "message": "Get the desktop app" + }, + "getTheDesktopAppDesc": { + "message": "Access your vault without a browser, then set up unlock with biometrics to expedite unlocking in both the desktop app and browser extension." + }, + "downloadFromBitwardenNow": { + "message": "Download from bitwarden.com now" + }, + "getItOnGooglePlay": { + "message": "Consíguela en Google Play" + }, + "downloadOnTheAppStore": { + "message": "Descarga en la App Store" + }, "permanentlyDeleteAttachmentConfirmation": { "message": "¿Estás seguro de que deseas eliminar permanentemente este adjunto?" }, @@ -4515,10 +4624,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 +4698,7 @@ "message": "Activar animaciones" }, "showAnimations": { - "message": "Show animations" + "message": "Mostrar animaciones" }, "addAccount": { "message": "Añadir cuenta" @@ -4661,10 +4770,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 +4937,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" @@ -4917,6 +5026,15 @@ "biometricsStatusHelptextUnavailableReasonUnknown": { "message": "Biometric unlock is currently unavailable for an unknown reason." }, + "unlockVault": { + "message": "Unlock your vault in seconds" + }, + "unlockVaultDesc": { + "message": "You can customize your unlock and timeout settings to more quickly access your vault." + }, + "unlockPinSet": { + "message": "Unlock PIN set" + }, "authenticating": { "message": "Autenticando" }, @@ -4928,8 +5046,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": { @@ -5079,42 +5197,6 @@ "beta": { "message": "Beta" }, - "importantNotice": { - "message": "Important notice" - }, - "setupTwoStepLogin": { - "message": "Set up two-step login" - }, - "newDeviceVerificationNoticeContentPage1": { - "message": "Bitwarden will send a code to your account email to verify logins from new devices starting in February 2025." - }, - "newDeviceVerificationNoticeContentPage2": { - "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" - }, - "newDeviceVerificationNoticePageOneFormContent": { - "message": "Do you have reliable access to your email, $EMAIL$?", - "placeholders": { - "email": { - "content": "$1", - "example": "your_name@email.com" - } - } - }, - "newDeviceVerificationNoticePageOneEmailAccessNo": { - "message": "No, I do not" - }, - "newDeviceVerificationNoticePageOneEmailAccessYes": { - "message": "Sí, puedo acceder a mi correo electrónico de forma fiable" - }, - "turnOnTwoStepLogin": { - "message": "Activar inicio de sesión en dos pasos" - }, - "changeAcctEmail": { - "message": "Cambiar la cuenta de correo electrónico" - }, "extensionWidth": { "message": "Ancho de extensión" }, @@ -5125,19 +5207,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 +5249,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 +5276,105 @@ "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." + }, + "nudgeBadgeAria": { + "message": "1 notification" + }, + "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!" + }, + "hasItemsVaultNudgeBodyOne": { + "message": "Autofill items for the current page" + }, + "hasItemsVaultNudgeBodyTwo": { + "message": "Favorite items for easy access" + }, + "hasItemsVaultNudgeBodyThree": { + "message": "Search your vault for something else" + }, + "newLoginNudgeTitle": { + "message": "Save time with autofill" + }, + "newLoginNudgeBodyOne": { + "message": "Include a", + "description": "This is in multiple parts to allow for bold text in the middle of the sentence.", + "example": "Include a Website so this login appears as an autofill suggestion." + }, + "newLoginNudgeBodyBold": { + "message": "Website", + "description": "This is in multiple parts to allow for bold text in the middle of the sentence.", + "example": "Include a Website so this login appears as an autofill suggestion." + }, + "newLoginNudgeBodyTwo": { + "message": "so this login appears as an autofill suggestion.", + "description": "This is in multiple parts to allow for bold text in the middle of the sentence.", + "example": "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" + }, + "newSshNudgeBodyOne": { + "message": "Store your keys and connect with the SSH agent for fast, encrypted authentication.", + "description": "Two part message", + "example": "Store your keys and connect with the SSH agent for fast, encrypted authentication. Learn more about SSH agent" + }, + "newSshNudgeBodyTwo": { + "message": "Learn more about SSH agent", + "description": "Two part message", + "example": "Store your keys and connect with the SSH agent for fast, encrypted authentication. Learn more about SSH agent" + }, + "generatorNudgeTitle": { + "message": "Quickly create passwords" + }, + "generatorNudgeBodyOne": { + "message": "Easily create strong and unique passwords by clicking on", + "description": "Two part message", + "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." + }, + "generatorNudgeBodyTwo": { + "message": "to help you keep your logins secure.", + "description": "Two part message", + "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." + }, + "generatorNudgeBodyAria": { + "message": "Easily create strong and unique passwords by clicking on the Generate password button to help you keep your logins secure.", + "description": "Aria label for the body content of the generator nudge" + }, + "noPermissionsViewPage": { + "message": "You do not have permissions to view this page. Try logging in with a different account." } } diff --git a/apps/browser/src/_locales/et/messages.json b/apps/browser/src/_locales/et/messages.json index f9f7cb950fe..0599339b77d 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,55 @@ "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.", + "notificationNewItemAria": { + "message": "New Item, opens in new window", + "description": "Aria label for the new item button in notification bar confirmation message when error is prompted" + }, + "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": { - "content": "$1" + "label": { + "content": "$1", + "example": "Login" } - }, - "description": "Shown to user after login is updated." + } + }, + "notificationLoginSaveConfirmation": { + "message": "saved to Bitwarden.", + "description": "Shown to user after item is saved." + }, + "notificationLoginUpdatedConfirmation": { + "message": "updated in Bitwarden.", + "description": "Shown to user after item is updated." + }, + "selectItemAriaLabel": { + "message": "Select $ITEMTYPE$, $ITEMNAME$", + "description": "Used by screen readers. $1 is the item type (like vault or folder), $2 is the selected item name.", + "placeholders": { + "itemType": { + "content": "$1" + }, + "itemName": { + "content": "$2" + } + } }, "saveAsNewLoginAction": { "message": "Save as new login", @@ -1082,12 +1120,16 @@ "message": "Update login", "description": "Button text for updating an existing login entry." }, - "saveLoginPrompt": { - "message": "Save login?", + "unlockToSave": { + "message": "Unlock to save this login", + "description": "User prompt to take action in order to save the login they just entered." + }, + "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": { @@ -1323,8 +1365,8 @@ "featureUnavailable": { "message": "Funktsioon pole saadaval" }, - "encryptionKeyMigrationRequired": { - "message": "Encryption key migration required. Please login through the web vault to update your encryption key." + "legacyEncryptionUnsupported": { + "message": "Legacy encryption is no longer supported. Please contact support to recover your account." }, "premiumMembership": { "message": "Premium versioon" @@ -1557,6 +1599,24 @@ "autofillSuggestionsSectionTitle": { "message": "Autofill suggestions" }, + "autofillSpotlightTitle": { + "message": "Easily find autofill suggestions" + }, + "autofillSpotlightDesc": { + "message": "Turn off your browser's autofill settings, so they don't conflict with Bitwarden." + }, + "turnOffBrowserAutofill": { + "message": "Turn off $BROWSER$ autofill", + "placeholders": { + "browser": { + "content": "$1", + "example": "Chrome" + } + } + }, + "turnOffAutofill": { + "message": "Turn off autofill" + }, "showInlineMenuLabel": { "message": "Show autofill suggestions on form fields" }, @@ -2144,6 +2204,9 @@ "useThisPassword": { "message": "Kasuta seda parooli" }, + "useThisPassphrase": { + "message": "Use this passphrase" + }, "useThisUsername": { "message": "Kasuta seda kasutajanime" }, @@ -2167,15 +2230,6 @@ "vaultTimeoutAction1": { "message": "Timeout action" }, - "newCustomizationOptionsCalloutTitle": { - "message": "New customization options" - }, - "newCustomizationOptionsCalloutContent": { - "message": "Customize your vault experience with quick copy actions, compact mode, and more!" - }, - "newCustomizationOptionsCalloutLink": { - "message": "View all Appearance settings" - }, "lock": { "message": "Lukusta", "description": "Verb form: to make secure or inaccessible by" @@ -2623,6 +2677,10 @@ "message": "Kõik Sendid", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, + "maxAccessCountReached": { + "message": "Max access count reached", + "description": "This text will be displayed after a Send has been accessed the maximum amount of times." + }, "hideTextByDefault": { "message": "Hide text by default" }, @@ -2963,14 +3021,14 @@ "copyCustomFieldNameNotUnique": { "message": "Unikaalset identifikaatorit ei leitud." }, - "convertOrganizationEncryptionDesc": { - "message": "$ORGANIZATION$ kasutab SSO-d koos enda majutatud võtmeserveriga. Selle organisatsiooni liikmed ei pea sisselogimisel enam ülemparooli kasutama.", - "placeholders": { - "organization": { - "content": "$1", - "example": "My Org Name" - } - } + "removeMasterPasswordForOrganizationUserKeyConnector": { + "message": "A master password is no longer required for members of the following organization. Please confirm the domain below with your organization administrator." + }, + "organizationName": { + "message": "Organization name" + }, + "keyConnectorDomain": { + "message": "Key Connector domain" }, "leaveOrganization": { "message": "Lahku organisatsioonist" @@ -3532,12 +3590,36 @@ "deviceTrusted": { "message": "Seade on usaldusväärne" }, - "sendsNoItemsTitle": { - "message": "No active Sends", + "trustOrganization": { + "message": "Trust organization" + }, + "trust": { + "message": "Trust" + }, + "doNotTrust": { + "message": "Do not trust" + }, + "organizationNotTrusted": { + "message": "Organization is not trusted" + }, + "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" + }, + "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." + }, + "orgTrustWarning1": { + "message": "This organization has an Enterprise policy that will enroll you in account recovery. Enrollment will allow organization administrators to change your password. Only proceed if you recognize this organization and the fingerprint phrase displayed below matches the organization's fingerprint." + }, + "trustUser": { + "message": "Trust user" + }, + "sendsTitleNoItems": { + "message": "Send sensitive information safely", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, - "sendsNoItemsMessage": { - "message": "Use Send to securely share encrypted information with anyone.", + "sendsBodyNoItems": { + "message": "Share files and data securely with anyone, on any platform. Your information will remain end-to-end encrypted while limiting exposure.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "inputRequired": { @@ -4456,6 +4538,33 @@ } } }, + "downloadBitwarden": { + "message": "Download Bitwarden" + }, + "downloadBitwardenOnAllDevices": { + "message": "Download Bitwarden on all devices" + }, + "getTheMobileApp": { + "message": "Get the mobile app" + }, + "getTheMobileAppDesc": { + "message": "Access your passwords on the go with the Bitwarden mobile app." + }, + "getTheDesktopApp": { + "message": "Get the desktop app" + }, + "getTheDesktopAppDesc": { + "message": "Access your vault without a browser, then set up unlock with biometrics to expedite unlocking in both the desktop app and browser extension." + }, + "downloadFromBitwardenNow": { + "message": "Download from bitwarden.com now" + }, + "getItOnGooglePlay": { + "message": "Get it on Google Play" + }, + "downloadOnTheAppStore": { + "message": "Download on the App Store" + }, "permanentlyDeleteAttachmentConfirmation": { "message": "Are you sure you want to permanently delete this attachment?" }, @@ -4917,6 +5026,15 @@ "biometricsStatusHelptextUnavailableReasonUnknown": { "message": "Biometric unlock is currently unavailable for an unknown reason." }, + "unlockVault": { + "message": "Unlock your vault in seconds" + }, + "unlockVaultDesc": { + "message": "You can customize your unlock and timeout settings to more quickly access your vault." + }, + "unlockPinSet": { + "message": "Unlock PIN set" + }, "authenticating": { "message": "Authenticating" }, @@ -4928,8 +5046,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": { @@ -5079,42 +5197,6 @@ "beta": { "message": "Beta" }, - "importantNotice": { - "message": "Important notice" - }, - "setupTwoStepLogin": { - "message": "Set up two-step login" - }, - "newDeviceVerificationNoticeContentPage1": { - "message": "Bitwarden will send a code to your account email to verify logins from new devices starting in February 2025." - }, - "newDeviceVerificationNoticeContentPage2": { - "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" - }, - "newDeviceVerificationNoticePageOneFormContent": { - "message": "Do you have reliable access to your email, $EMAIL$?", - "placeholders": { - "email": { - "content": "$1", - "example": "your_name@email.com" - } - } - }, - "newDeviceVerificationNoticePageOneEmailAccessNo": { - "message": "No, I do not" - }, - "newDeviceVerificationNoticePageOneEmailAccessYes": { - "message": "Yes, I can reliably access my email" - }, - "turnOnTwoStepLogin": { - "message": "Turn on two-step login" - }, - "changeAcctEmail": { - "message": "Change account email" - }, "extensionWidth": { "message": "Extension width" }, @@ -5169,6 +5251,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 +5283,98 @@ }, "secureDevicesBody": { "message": "Save unlimited passwords across unlimited devices with Bitwarden mobile, browser, and desktop apps." + }, + "nudgeBadgeAria": { + "message": "1 notification" + }, + "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!" + }, + "hasItemsVaultNudgeBodyOne": { + "message": "Autofill items for the current page" + }, + "hasItemsVaultNudgeBodyTwo": { + "message": "Favorite items for easy access" + }, + "hasItemsVaultNudgeBodyThree": { + "message": "Search your vault for something else" + }, + "newLoginNudgeTitle": { + "message": "Save time with autofill" + }, + "newLoginNudgeBodyOne": { + "message": "Include a", + "description": "This is in multiple parts to allow for bold text in the middle of the sentence.", + "example": "Include a Website so this login appears as an autofill suggestion." + }, + "newLoginNudgeBodyBold": { + "message": "Website", + "description": "This is in multiple parts to allow for bold text in the middle of the sentence.", + "example": "Include a Website so this login appears as an autofill suggestion." + }, + "newLoginNudgeBodyTwo": { + "message": "so this login appears as an autofill suggestion.", + "description": "This is in multiple parts to allow for bold text in the middle of the sentence.", + "example": "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" + }, + "newSshNudgeBodyOne": { + "message": "Store your keys and connect with the SSH agent for fast, encrypted authentication.", + "description": "Two part message", + "example": "Store your keys and connect with the SSH agent for fast, encrypted authentication. Learn more about SSH agent" + }, + "newSshNudgeBodyTwo": { + "message": "Learn more about SSH agent", + "description": "Two part message", + "example": "Store your keys and connect with the SSH agent for fast, encrypted authentication. Learn more about SSH agent" + }, + "generatorNudgeTitle": { + "message": "Quickly create passwords" + }, + "generatorNudgeBodyOne": { + "message": "Easily create strong and unique passwords by clicking on", + "description": "Two part message", + "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." + }, + "generatorNudgeBodyTwo": { + "message": "to help you keep your logins secure.", + "description": "Two part message", + "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." + }, + "generatorNudgeBodyAria": { + "message": "Easily create strong and unique passwords by clicking on the Generate password button to help you keep your logins secure.", + "description": "Aria label for the body content of the generator nudge" + }, + "noPermissionsViewPage": { + "message": "You do not have permissions to view this page. Try logging in with a different account." } } diff --git a/apps/browser/src/_locales/eu/messages.json b/apps/browser/src/_locales/eu/messages.json index ec1f5189946..4a4713737fa 100644 --- a/apps/browser/src/_locales/eu/messages.json +++ b/apps/browser/src/_locales/eu/messages.json @@ -2,8 +2,11 @@ "appName": { "message": "Bitwarden" }, + "appLogoLabel": { + "message": "Bitwarden logo" + }, "extName": { - "message": "Bitwarden Password Manager", + "message": "Bitwarden pasahitz kudeatzailea", "description": "Extension name, MUST be less than 40 characters (Safari restriction)" }, "extDesc": { @@ -14,7 +17,7 @@ "message": "Saioa hasi edo sortu kontu berri bat zure kutxa gotorrera sartzeko." }, "inviteAccepted": { - "message": "Invitation accepted" + "message": "Gonbidapena onartua" }, "createAccount": { "message": "Sortu kontua" @@ -29,13 +32,13 @@ "message": "Use single sign-on" }, "welcomeBack": { - "message": "Welcome back" + "message": "Ongi etorri berriro ere" }, "setAStrongPassword": { - "message": "Set a strong password" + "message": "Pasahitz sendo bat ezarri" }, "finishCreatingYourAccountBySettingAPassword": { - "message": "Finish creating your account by setting a password" + "message": "Amaitu zure kontua sortzen pasahitza ezarriz" }, "enterpriseSingleSignOn": { "message": "Enpresentzako saio hasiera bakarra" @@ -147,7 +150,7 @@ "message": "Kopiatu segurtasun-kodea" }, "copyName": { - "message": "Copy name" + "message": "Izena kopiatu" }, "copyCompany": { "message": "Copy company" @@ -183,14 +186,14 @@ "message": "Copy website" }, "copyNotes": { - "message": "Copy notes" + "message": "Kopiatu oharrak" }, "copy": { - "message": "Copy", + "message": "Kopiatu", "description": "Copy to clipboard" }, "fill": { - "message": "Fill", + "message": "Bete", "description": "This string is used on the vault page to indicate autofilling. Horizontal space is limited in the interface here so try and keep translations as concise as possible." }, "autoFill": { @@ -206,10 +209,10 @@ "message": "Auto-bete nortasuna" }, "fillVerificationCode": { - "message": "Fill verification code" + "message": "Bete egiaztapen-kodea" }, "fillVerificationCodeAria": { - "message": "Fill Verification Code", + "message": "Bete egiaztapen-kodea", "description": "Aria label for the heading displayed the inline menu for totp code autofill" }, "generatePasswordCopied": { @@ -228,7 +231,7 @@ "message": "Nortasunik ez" }, "addLoginMenu": { - "message": "Add login" + "message": "Gehitu logina" }, "addCardMenu": { "message": "Gehitu txartela" @@ -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,55 @@ "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.", + "notificationNewItemAria": { + "message": "New Item, opens in new window", + "description": "Aria label for the new item button in notification bar confirmation message when error is prompted" + }, + "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": { - "content": "$1" + "label": { + "content": "$1", + "example": "Login" } - }, - "description": "Shown to user after login is updated." + } + }, + "notificationLoginSaveConfirmation": { + "message": "saved to Bitwarden.", + "description": "Shown to user after item is saved." + }, + "notificationLoginUpdatedConfirmation": { + "message": "updated in Bitwarden.", + "description": "Shown to user after item is updated." + }, + "selectItemAriaLabel": { + "message": "Select $ITEMTYPE$, $ITEMNAME$", + "description": "Used by screen readers. $1 is the item type (like vault or folder), $2 is the selected item name.", + "placeholders": { + "itemType": { + "content": "$1" + }, + "itemName": { + "content": "$2" + } + } }, "saveAsNewLoginAction": { "message": "Save as new login", @@ -1082,12 +1120,16 @@ "message": "Update login", "description": "Button text for updating an existing login entry." }, - "saveLoginPrompt": { - "message": "Save login?", + "unlockToSave": { + "message": "Unlock to save this login", + "description": "User prompt to take action in order to save the login they just entered." + }, + "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": { @@ -1323,8 +1365,8 @@ "featureUnavailable": { "message": "Ezaugarria ez dago erabilgarri" }, - "encryptionKeyMigrationRequired": { - "message": "Encryption key migration required. Please login through the web vault to update your encryption key." + "legacyEncryptionUnsupported": { + "message": "Legacy encryption is no longer supported. Please contact support to recover your account." }, "premiumMembership": { "message": "Premium bazkidea" @@ -1557,6 +1599,24 @@ "autofillSuggestionsSectionTitle": { "message": "Autofill suggestions" }, + "autofillSpotlightTitle": { + "message": "Easily find autofill suggestions" + }, + "autofillSpotlightDesc": { + "message": "Turn off your browser's autofill settings, so they don't conflict with Bitwarden." + }, + "turnOffBrowserAutofill": { + "message": "Turn off $BROWSER$ autofill", + "placeholders": { + "browser": { + "content": "$1", + "example": "Chrome" + } + } + }, + "turnOffAutofill": { + "message": "Turn off autofill" + }, "showInlineMenuLabel": { "message": "Show autofill suggestions on form fields" }, @@ -2144,6 +2204,9 @@ "useThisPassword": { "message": "Use this password" }, + "useThisPassphrase": { + "message": "Use this passphrase" + }, "useThisUsername": { "message": "Use this username" }, @@ -2167,15 +2230,6 @@ "vaultTimeoutAction1": { "message": "Timeout action" }, - "newCustomizationOptionsCalloutTitle": { - "message": "New customization options" - }, - "newCustomizationOptionsCalloutContent": { - "message": "Customize your vault experience with quick copy actions, compact mode, and more!" - }, - "newCustomizationOptionsCalloutLink": { - "message": "View all Appearance settings" - }, "lock": { "message": "Blokeatu", "description": "Verb form: to make secure or inaccessible by" @@ -2623,6 +2677,10 @@ "message": "Send guztiak", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, + "maxAccessCountReached": { + "message": "Max access count reached", + "description": "This text will be displayed after a Send has been accessed the maximum amount of times." + }, "hideTextByDefault": { "message": "Hide text by default" }, @@ -2963,14 +3021,14 @@ "copyCustomFieldNameNotUnique": { "message": "Ez da identifikatzaile bakarrik aurkitu." }, - "convertOrganizationEncryptionDesc": { - "message": "$ORGANIZATION$ SSO erabiltzen ari da ostatatze propioa duen gako-zerbitzari batekin. Dagoeneko ez da pasahitz nagusirik behar erakunde honetako kideentzat saioa hasteko.", - "placeholders": { - "organization": { - "content": "$1", - "example": "My Org Name" - } - } + "removeMasterPasswordForOrganizationUserKeyConnector": { + "message": "A master password is no longer required for members of the following organization. Please confirm the domain below with your organization administrator." + }, + "organizationName": { + "message": "Organization name" + }, + "keyConnectorDomain": { + "message": "Key Connector domain" }, "leaveOrganization": { "message": "Utzi erakundea" @@ -3532,12 +3590,36 @@ "deviceTrusted": { "message": "Device trusted" }, - "sendsNoItemsTitle": { - "message": "No active Sends", + "trustOrganization": { + "message": "Trust organization" + }, + "trust": { + "message": "Trust" + }, + "doNotTrust": { + "message": "Do not trust" + }, + "organizationNotTrusted": { + "message": "Organization is not trusted" + }, + "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" + }, + "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." + }, + "orgTrustWarning1": { + "message": "This organization has an Enterprise policy that will enroll you in account recovery. Enrollment will allow organization administrators to change your password. Only proceed if you recognize this organization and the fingerprint phrase displayed below matches the organization's fingerprint." + }, + "trustUser": { + "message": "Trust user" + }, + "sendsTitleNoItems": { + "message": "Send sensitive information safely", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, - "sendsNoItemsMessage": { - "message": "Use Send to securely share encrypted information with anyone.", + "sendsBodyNoItems": { + "message": "Share files and data securely with anyone, on any platform. Your information will remain end-to-end encrypted while limiting exposure.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "inputRequired": { @@ -4456,6 +4538,33 @@ } } }, + "downloadBitwarden": { + "message": "Download Bitwarden" + }, + "downloadBitwardenOnAllDevices": { + "message": "Download Bitwarden on all devices" + }, + "getTheMobileApp": { + "message": "Get the mobile app" + }, + "getTheMobileAppDesc": { + "message": "Access your passwords on the go with the Bitwarden mobile app." + }, + "getTheDesktopApp": { + "message": "Get the desktop app" + }, + "getTheDesktopAppDesc": { + "message": "Access your vault without a browser, then set up unlock with biometrics to expedite unlocking in both the desktop app and browser extension." + }, + "downloadFromBitwardenNow": { + "message": "Download from bitwarden.com now" + }, + "getItOnGooglePlay": { + "message": "Get it on Google Play" + }, + "downloadOnTheAppStore": { + "message": "Download on the App Store" + }, "permanentlyDeleteAttachmentConfirmation": { "message": "Are you sure you want to permanently delete this attachment?" }, @@ -4917,6 +5026,15 @@ "biometricsStatusHelptextUnavailableReasonUnknown": { "message": "Biometric unlock is currently unavailable for an unknown reason." }, + "unlockVault": { + "message": "Unlock your vault in seconds" + }, + "unlockVaultDesc": { + "message": "You can customize your unlock and timeout settings to more quickly access your vault." + }, + "unlockPinSet": { + "message": "Unlock PIN set" + }, "authenticating": { "message": "Authenticating" }, @@ -4928,8 +5046,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": { @@ -5068,53 +5186,17 @@ "message": "Lowercase" }, "uppercaseAriaLabel": { - "message": "Uppercase" + "message": "Maiuskulak" }, "generatedPassword": { - "message": "Generated password" + "message": "Sortutako pasahitza" }, "compactMode": { - "message": "Compact mode" + "message": "Modu trinkoa" }, "beta": { "message": "Beta" }, - "importantNotice": { - "message": "Important notice" - }, - "setupTwoStepLogin": { - "message": "Set up two-step login" - }, - "newDeviceVerificationNoticeContentPage1": { - "message": "Bitwarden will send a code to your account email to verify logins from new devices starting in February 2025." - }, - "newDeviceVerificationNoticeContentPage2": { - "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" - }, - "newDeviceVerificationNoticePageOneFormContent": { - "message": "Do you have reliable access to your email, $EMAIL$?", - "placeholders": { - "email": { - "content": "$1", - "example": "your_name@email.com" - } - } - }, - "newDeviceVerificationNoticePageOneEmailAccessNo": { - "message": "No, I do not" - }, - "newDeviceVerificationNoticePageOneEmailAccessYes": { - "message": "Yes, I can reliably access my email" - }, - "turnOnTwoStepLogin": { - "message": "Turn on two-step login" - }, - "changeAcctEmail": { - "message": "Change account email" - }, "extensionWidth": { "message": "Extension width" }, @@ -5169,6 +5251,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 +5283,98 @@ }, "secureDevicesBody": { "message": "Save unlimited passwords across unlimited devices with Bitwarden mobile, browser, and desktop apps." + }, + "nudgeBadgeAria": { + "message": "1 notification" + }, + "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!" + }, + "hasItemsVaultNudgeBodyOne": { + "message": "Autofill items for the current page" + }, + "hasItemsVaultNudgeBodyTwo": { + "message": "Favorite items for easy access" + }, + "hasItemsVaultNudgeBodyThree": { + "message": "Search your vault for something else" + }, + "newLoginNudgeTitle": { + "message": "Save time with autofill" + }, + "newLoginNudgeBodyOne": { + "message": "Include a", + "description": "This is in multiple parts to allow for bold text in the middle of the sentence.", + "example": "Include a Website so this login appears as an autofill suggestion." + }, + "newLoginNudgeBodyBold": { + "message": "Website", + "description": "This is in multiple parts to allow for bold text in the middle of the sentence.", + "example": "Include a Website so this login appears as an autofill suggestion." + }, + "newLoginNudgeBodyTwo": { + "message": "so this login appears as an autofill suggestion.", + "description": "This is in multiple parts to allow for bold text in the middle of the sentence.", + "example": "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" + }, + "newSshNudgeBodyOne": { + "message": "Store your keys and connect with the SSH agent for fast, encrypted authentication.", + "description": "Two part message", + "example": "Store your keys and connect with the SSH agent for fast, encrypted authentication. Learn more about SSH agent" + }, + "newSshNudgeBodyTwo": { + "message": "Learn more about SSH agent", + "description": "Two part message", + "example": "Store your keys and connect with the SSH agent for fast, encrypted authentication. Learn more about SSH agent" + }, + "generatorNudgeTitle": { + "message": "Quickly create passwords" + }, + "generatorNudgeBodyOne": { + "message": "Easily create strong and unique passwords by clicking on", + "description": "Two part message", + "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." + }, + "generatorNudgeBodyTwo": { + "message": "to help you keep your logins secure.", + "description": "Two part message", + "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." + }, + "generatorNudgeBodyAria": { + "message": "Easily create strong and unique passwords by clicking on the Generate password button to help you keep your logins secure.", + "description": "Aria label for the body content of the generator nudge" + }, + "noPermissionsViewPage": { + "message": "You do not have permissions to view this page. Try logging in with a different account." } } diff --git a/apps/browser/src/_locales/fa/messages.json b/apps/browser/src/_locales/fa/messages.json index 0e5e8943b7d..1b1b865e1d0 100644 --- a/apps/browser/src/_locales/fa/messages.json +++ b/apps/browser/src/_locales/fa/messages.json @@ -2,8 +2,11 @@ "appName": { "message": "Bitwarden" }, + "appLogoLabel": { + "message": "لوگو Bitwarden" + }, "extName": { - "message": "مدیریت رمز عبور Bitwarden", + "message": "مدیریت کلمه عبور Bitwarden", "description": "Extension name, MUST be less than 40 characters (Safari restriction)" }, "extDesc": { @@ -14,28 +17,28 @@ "message": "وارد شوید یا یک حساب کاربری بسازید تا به گاوصندوق امن‌تان دسترسی یابید." }, "inviteAccepted": { - "message": "Invitation accepted" + "message": "دعوتنامه پذیرفته شد" }, "createAccount": { "message": "ایجاد حساب کاربری" }, "newToBitwarden": { - "message": "New to Bitwarden?" + "message": "در Bitwarden تازه وارد هستید؟" }, "logInWithPasskey": { - "message": "Log in with passkey" + "message": "با کلید عبور وارد شوید" }, "useSingleSignOn": { - "message": "Use single sign-on" + "message": "استفاده از ورود تک مرحله‌ای" }, "welcomeBack": { - "message": "Welcome back" + "message": "خوش آمدید" }, "setAStrongPassword": { - "message": "تنظیم رمز عبور قوی" + "message": "تنظیم کلمه عبور قوی" }, "finishCreatingYourAccountBySettingAPassword": { - "message": "ایجاد حساب خود را با تنظیم رمز عبور تکمیل کنید" + "message": "ایجاد حساب کاربری خود را با تنظیم کلمه عبور تکمیل کنید" }, "enterpriseSingleSignOn": { "message": "ورود به سیستم پروژه" @@ -62,7 +65,7 @@ "message": "یادآور کلمه عبور اصلی کمک می‌کند در صورت فراموشی آن را به یاد بیارید." }, "masterPassHintText": { - "message": "If you forget your password, the password hint can be sent to your email. $CURRENT$/$MAXIMUM$ character maximum.", + "message": "اگر کلمه عبور خود را فراموش کنید، یادآور کلمه عبور می‌تواند به ایمیل شما ارسال شود. حداکثر $CURRENT$/$MAXIMUM$ کاراکتر.", "placeholders": { "current": { "content": "$1", @@ -81,7 +84,7 @@ "message": "یادآور کلمه عبور اصلی (اختیاری)" }, "passwordStrengthScore": { - "message": "Password strength score $SCORE$", + "message": "امتیاز قدرت کلمه عبور $SCORE$", "placeholders": { "score": { "content": "$1", @@ -90,10 +93,10 @@ } }, "joinOrganization": { - "message": "Join organization" + "message": "به سازمان بپیوندید" }, "joinOrganizationName": { - "message": "Join $ORGANIZATIONNAME$", + "message": "به $ORGANIZATIONNAME$ بپیوندید", "placeholders": { "organizationName": { "content": "$1", @@ -102,7 +105,7 @@ } }, "finishJoiningThisOrganizationBySettingAMasterPassword": { - "message": "Finish joining this organization by setting a master password." + "message": "با تعیین یک کلمه عبور اصلی، عضویت خود در این سازمان را کامل کنید." }, "tab": { "message": "زبانه" @@ -129,7 +132,7 @@ "message": "کپی کلمه عبور" }, "copyPassphrase": { - "message": "Copy passphrase" + "message": "کپی عبارت عبور" }, "copyNote": { "message": "کپی یادداشت" @@ -147,31 +150,31 @@ "message": "کپی کد امنیتی" }, "copyName": { - "message": "Copy name" + "message": "کپی نام" }, "copyCompany": { - "message": "Copy company" + "message": "کپی شرکت" }, "copySSN": { - "message": "Copy Social Security number" + "message": "کپی شماره کد ملی" }, "copyPassportNumber": { - "message": "Copy passport number" + "message": "کپی شماره گذرنامه" }, "copyLicenseNumber": { - "message": "Copy license number" + "message": "کپی شماره گواهینامه" }, "copyPrivateKey": { - "message": "Copy private key" + "message": "کپی کلید خصوصی" }, "copyPublicKey": { - "message": "Copy public key" + "message": "کپی کلید عمومی" }, "copyFingerprint": { - "message": "Copy fingerprint" + "message": "کپی اثر انگشت" }, "copyCustomField": { - "message": "Copy $FIELD$", + "message": "کپی $FIELD$", "placeholders": { "field": { "content": "$1", @@ -180,17 +183,17 @@ } }, "copyWebsite": { - "message": "Copy website" + "message": "کپی وب‌سایت" }, "copyNotes": { - "message": "Copy notes" + "message": "کپی یادداشت‌ها" }, "copy": { - "message": "Copy", + "message": "کپی", "description": "Copy to clipboard" }, "fill": { - "message": "Fill", + "message": "پر کردن", "description": "This string is used on the vault page to indicate autofilling. Horizontal space is limited in the interface here so try and keep translations as concise as possible." }, "autoFill": { @@ -206,10 +209,10 @@ "message": "پر کردن خودکار هویت" }, "fillVerificationCode": { - "message": "Fill verification code" + "message": "پر کردن کد تأیید" }, "fillVerificationCodeAria": { - "message": "Fill Verification Code", + "message": "پر کردن کد تأیید", "description": "Aria label for the heading displayed the inline menu for totp code autofill" }, "generatePasswordCopied": { @@ -252,16 +255,16 @@ "message": "افزودن مورد" }, "accountEmail": { - "message": "Account email" + "message": "حساب ایمیل" }, "requestHint": { - "message": "Request hint" + "message": "درخواست راهنمایی" }, "requestPasswordHint": { - "message": "Request password hint" + "message": "درخواست یادآور کلمه عبور" }, "enterYourAccountEmailAddressAndYourPasswordHintWillBeSentToYou": { - "message": "Enter your account email address and your password hint will be sent to you" + "message": "نشانی ایمیل حساب کاربری خود را وارد کنید تا راهنمای کلمه عبور برای شما ارسال شود" }, "getMasterPasswordHint": { "message": "دریافت یادآور کلمه عبور اصلی" @@ -288,25 +291,25 @@ "message": "تغییر کلمه عبور اصلی" }, "continueToWebApp": { - "message": "Continue to web app?" + "message": "با برنامه وب ادامه می‌دهید؟" }, "continueToWebAppDesc": { "message": "ویژگی‌های بیشتر حساب Bitwarden خود را در برنامه وب کاوش کنید." }, "continueToHelpCenter": { - "message": "Continue to Help Center?" + "message": "به مرکز راهنمایی ادامه می‌دهید؟" }, "continueToHelpCenterDesc": { "message": "درباره استفاده از Bitwarden در مرکز راهنما بیشتر بیاموزید." }, "continueToBrowserExtensionStore": { - "message": "آیا میخواهید به فروشگاه افزونه مرورگر ادامه دهید?" + "message": "آیا می‌خواهید به فروشگاه افزونه مرورگر ادامه دهید؟" }, "continueToBrowserExtensionStoreDesc": { "message": "به دیگران کمک کنید تا بفهمند آیا Bitwarden برایشان مناسب است یا نه. به فروشگاه افزونه مرورگر خود بروید و نظر خود را به اشتراک بگذارید." }, "changeMasterPasswordOnWebConfirmation": { - "message": "You can change your master password on the Bitwarden web app." + "message": "می‌توانید کلمه عبور اصلی خود را در برنامه وب Bitwarden تغییر دهید." }, "fingerprintPhrase": { "message": "عبارت اثر انگشت", @@ -329,37 +332,37 @@ "message": "درباره" }, "moreFromBitwarden": { - "message": "More from Bitwarden" + "message": "موارد بیشتر از Bitwarden" }, "continueToBitwardenDotCom": { - "message": "Continue to bitwarden.com?" + "message": "به bitwarden.com ادامه می‌دهید؟" }, "bitwardenForBusiness": { "message": "Bitwarden برای کسب و کارها" }, "bitwardenAuthenticator": { - "message": "تاییدکننده هویت Bitwarden" + "message": "تأییدکننده هویت Bitwarden" }, "continueToAuthenticatorPageDesc": { - "message": "Bitwarden Authenticator allows you to store authenticator keys and generate TOTP codes for 2-step verification flows. Learn more on the bitwarden.com website" + "message": "احراز هویت کننده Bitwarden به شما امکان می‌دهد کلیدهای احراز هویت را ذخیره کرده و کدهای TOTP را برای فرآیندهای تأیید دومرحله‌ای تولید کنید. برای اطلاعات بیشتر به وب‌سایت bitwarden.com مراجعه کنید" }, "bitwardenSecretsManager": { - "message": "Bitwarden Secrets Manager" + "message": "مدیر اسرار Bitwarden" }, "continueToSecretsManagerPageDesc": { - "message": "Securely store, manage, and share developer secrets with Bitwarden Secrets Manager. Learn more on the bitwarden.com website." + "message": "اسرار توسعه دهنده را با مدیر اسرا Bitwarden به‌صورت ایمن ذخیره، مدیریت و به اشتراک بگذارید. برای اطلاعات بیشتر به وب‌سایت bitwarden.com مراجعه کنید." }, "passwordlessDotDev": { "message": "Passwordless.dev" }, "continueToPasswordlessDotDevPageDesc": { - "message": "Create smooth and secure login experiences free from traditional passwords with Passwordless.dev. Learn more on the bitwarden.com website." + "message": "با استفاده از Passwordless.dev تجربه‌های ورود روان و ایمنی را بدون نیاز به کلمات عبور سنتی ایجاد کنید. برای اطلاعات بیشتر به وب‌سایت bitwarden.com مراجعه کنید." }, "freeBitwardenFamilies": { "message": "خانواده‌های رایگان Bitwarden" }, "freeBitwardenFamiliesPageDesc": { - "message": "You are eligible for Free Bitwarden Families. Redeem this offer today in the web app." + "message": "شما واجد شرایط استفاده رایگان از Bitwarden Families هستید. این پیشنهاد را امروز در نسخه وب دریافت کنید." }, "version": { "message": "نسخه" @@ -380,7 +383,7 @@ "message": "ويرايش پوشه" }, "editFolderWithName": { - "message": "Edit folder: $FOLDERNAME$", + "message": "ویرایش پوشه: $FOLDERNAME$", "placeholders": { "foldername": { "content": "$1", @@ -389,22 +392,22 @@ } }, "newFolder": { - "message": "New folder" + "message": "پوشه جدید" }, "folderName": { - "message": "Folder name" + "message": "نام پوشه" }, "folderHintText": { - "message": "Nest a folder by adding the parent folder's name followed by a “/”. Example: Social/Forums" + "message": "برای تو در تو کردن یک پوشه، نام پوشه والد را وارد کرده و سپس یک “/” اضافه کنید. مثال: Social/Forums" }, "noFoldersAdded": { - "message": "No folders added" + "message": "هیچ پوشه‌ای اضافه نشد" }, "createFoldersToOrganize": { - "message": "Create folders to organize your vault items" + "message": "برای سامان‌دهی موردهای گاوصندوق خود پوشه ایجاد کنید" }, "deleteFolderPermanently": { - "message": "Are you sure you want to permanently delete this folder?" + "message": "مطمئنید می‌خواهید این پوشه را برای همیشه پاک کنید؟" }, "deleteFolder": { "message": "حذف پوشه" @@ -447,7 +450,7 @@ "message": "به طور خودکار کلمه‌های عبور قوی و منحصر به فرد برای ورود به سیستم خود ایجاد کنید." }, "bitWebVaultApp": { - "message": "Bitwarden web app" + "message": "برنامه وب Bitwarden" }, "importItems": { "message": "درون ریزی موارد" @@ -459,19 +462,19 @@ "message": "تولید کلمه عبور" }, "generatePassphrase": { - "message": "Generate passphrase" + "message": "تولید عبارت عبور" }, "passwordGenerated": { - "message": "Password generated" + "message": "کلمه عبور تولید شد" }, "passphraseGenerated": { - "message": "Passphrase generated" + "message": "عبارت عبور تولید شد" }, "usernameGenerated": { - "message": "Username generated" + "message": "نام کاربری تولید شد" }, "emailGenerated": { - "message": "Email generated" + "message": "ایمیل تولید شد" }, "regeneratePassword": { "message": "تولید مجدد کلمه عبور" @@ -483,11 +486,11 @@ "message": "طول" }, "include": { - "message": "Include", + "message": "شامل", "description": "Card header for password generator include block" }, "uppercaseDescription": { - "message": "Include uppercase characters", + "message": "شامل حروف بزرگ باشد", "description": "Tooltip for the password generator uppercase character checkbox" }, "uppercaseLabel": { @@ -495,7 +498,7 @@ "description": "Label for the password generator uppercase character checkbox" }, "lowercaseDescription": { - "message": "Include lowercase characters", + "message": "شامل حروف کوچک باشد", "description": "Full description for the password generator lowercase character checkbox" }, "lowercaseLabel": { @@ -503,7 +506,7 @@ "description": "Label for the password generator lowercase character checkbox" }, "numbersDescription": { - "message": "Include numbers", + "message": "شامل اعداد", "description": "Full description for the password generator numbers checkbox" }, "numbersLabel": { @@ -511,7 +514,7 @@ "description": "Label for the password generator numbers checkbox" }, "specialCharactersDescription": { - "message": "Include special characters", + "message": "افزودن کاراکترهای خاص", "description": "Full description for the password generator special characters checkbox" }, "numWords": { @@ -534,11 +537,11 @@ "message": "حداقل حرف خاص" }, "avoidAmbiguous": { - "message": "Avoid ambiguous characters", + "message": "از کاراکترهای مبهم خودداری کن", "description": "Label for the avoid ambiguous characters checkbox." }, "generatorPolicyInEffect": { - "message": "Enterprise policy requirements have been applied to your generator options.", + "message": "نیازمندی‌های سیاست سازمانی بر گزینه‌های تولید کننده شما اعمال شده‌اند.", "description": "Indicates that a policy limits the credential generator screen." }, "searchVault": { @@ -563,7 +566,7 @@ "message": "کلمه عبور" }, "totp": { - "message": "Authenticator secret" + "message": "کلید مخفی احراز کننده هویت‌" }, "passphrase": { "message": "عبارت عبور" @@ -584,7 +587,7 @@ "message": "یادداشت‌ها" }, "privateNote": { - "message": "Private note" + "message": "یادداشت خصوصی" }, "note": { "message": "یادداشت" @@ -605,10 +608,10 @@ "message": "راه اندازی" }, "launchWebsite": { - "message": "Launch website" + "message": "باز کردن وب‌سایت" }, "launchWebsiteName": { - "message": "Launch website $ITEMNAME$", + "message": "باز کردن وب‌سایت $ITEMNAME$", "placeholders": { "itemname": { "content": "$1", @@ -629,19 +632,19 @@ "message": "ساير" }, "unlockMethods": { - "message": "Unlock options" + "message": "باز کردن امکانات" }, "unlockMethodNeededToChangeTimeoutActionDesc": { "message": "یک روش بازگشایی برای پایان زمان مجاز تنظیم کنید." }, "unlockMethodNeeded": { - "message": "Set up an unlock method in Settings" + "message": "یک روش باز کردن قفل را در تنظیمات راه‌اندازی کنید" }, "sessionTimeoutHeader": { - "message": "Session timeout" + "message": "پایان زمان نشست" }, "vaultTimeoutHeader": { - "message": "Vault timeout" + "message": "متوقف شدن گاو‌صندوق" }, "otherOptions": { "message": "سایر گزینه‌ها" @@ -653,25 +656,25 @@ "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": "گاوصندوق شما قفل شده است. برای ادامه هویت خود را تأیید کنید." }, "yourVaultIsLockedV2": { - "message": "Your vault is locked" + "message": "گاوصندوق‌تان قفل شد" }, "yourAccountIsLocked": { - "message": "Your account is locked" + "message": "حساب شما قفل شده است" }, "or": { - "message": "or" + "message": "یا" }, "unlock": { "message": "باز کردن قفل" @@ -696,13 +699,13 @@ "message": "متوقف شدن گاو‌صندوق" }, "vaultTimeout1": { - "message": "Timeout" + "message": "پایان زمان" }, "lockNow": { "message": "الان قفل شود" }, "lockAll": { - "message": "Lock all" + "message": "قفل کردن همه" }, "immediately": { "message": "بلافاصله" @@ -750,16 +753,16 @@ "message": "امنیت" }, "confirmMasterPassword": { - "message": "Confirm master password" + "message": "تأیید کلمه عبور اصلی" }, "masterPassword": { - "message": "Master password" + "message": "کلمه عبور اصلی" }, "masterPassImportant": { - "message": "Your master password cannot be recovered if you forget it!" + "message": "کلمه‌های عبور اصلی در صورت فراموشی قابل بازیابی نیستند!" }, "masterPassHintLabel": { - "message": "Master password hint" + "message": "یادآور کلمه عبور اصلی" }, "errorOccurred": { "message": "خطایی رخ داده است" @@ -793,10 +796,10 @@ "message": "حساب کاربری جدید شما ساخته شد! حالا می‌توانید وارد شوید." }, "newAccountCreated2": { - "message": "Your new account has been created!" + "message": "حساب کاربری جدید شما ایجاد شده است!" }, "youHaveBeenLoggedIn": { - "message": "You have been logged in!" + "message": "شما با موفقیت وارد شدید!" }, "youSuccessfullyLoggedIn": { "message": "شما با موفقیت وارد شدید" @@ -811,7 +814,7 @@ "message": "کد تأیید مورد نیاز است." }, "webauthnCancelOrTimeout": { - "message": "The authentication was cancelled or took too long. Please try again." + "message": "احراز هویت لغو شد یا بیش از حد طول کشید. لطفاً دوباره تلاش کنید." }, "invalidVerificationCode": { "message": "کد تأیید نامعتبر است" @@ -830,70 +833,73 @@ "message": "ناتوان در پر کردن خودکار مورد انتخاب شده در این صفحه. اطلاعات را کپی و جای‌گذاری کنید." }, "totpCaptureError": { - "message": "Unable to scan QR code from the current webpage" + "message": "امکان اسکن کد QR از صفحه وب فعلی وجود ندارد" }, "totpCaptureSuccess": { "message": "کلید احراز هویت اضافه شد" }, "totpCapture": { - "message": "Scan authenticator QR code from current webpage" + "message": "اسکن کد QR احراز هویت کننده از صفحه وب فعلی" }, "totpHelperTitle": { - "message": "Make 2-step verification seamless" + "message": "تأیید دو مرحله‌ای را بدون دردسر کنید" }, "totpHelper": { - "message": "Bitwarden can store and fill 2-step verification codes. Copy and paste the key into this field." + "message": "Bitwarden می‌تواند کدهای تأیید دو مرحله‌ای را ذخیره و پر کند. کلید را کپی کرده و در این فیلد قرار دهید." }, "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." + "message": "Bitwarden می‌تواند کدهای تأیید دو مرحله‌ای را ذخیره و پر کند. برای اسکن کد QR احراز هویت کننده این وب‌سایت، روی آیکون دوربین کلیک کنید یا کلید را کپی کرده و در این فیلد قرار دهید." }, "learnMoreAboutAuthenticators": { - "message": "Learn more about authenticators" + "message": "درباره احراز هویت کننده‌ها بیشتر بدانید" }, "copyTOTP": { - "message": "Copy Authenticator key (TOTP)" + "message": "کپی کلید احراز هویت (TOTP)" }, "loggedOut": { "message": "خارج شد" }, "loggedOutDesc": { - "message": "You have been logged out of your account." + "message": "شما از حساب خود خارج شده‌اید." }, "loginExpired": { "message": "نشست ورود شما منقضی شده است." }, "logIn": { - "message": "Log in" + "message": "ورود" }, "logInToBitwarden": { - "message": "Log in to Bitwarden" + "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": "برای حساب کاربری شما ورود دو مرحله‌ای Duo لازم است. مراحل زیر را دنبال کنید تا ورود خود را کامل کنید." }, "followTheStepsBelowToFinishLoggingIn": { - "message": "Follow the steps below to finish logging in." + "message": "مراحل زیر را دنبال کنید تا وارد سیستم شوید." + }, + "followTheStepsBelowToFinishLoggingInWithSecurityKey": { + "message": "مراحل زیر را برای کامل کردن ورود با کلید امنیتی خود دنبال کنید." }, "restartRegistration": { - "message": "Restart registration" + "message": "ثبت‌نام را دوباره آغاز کنید" }, "expiredLink": { - "message": "Expired link" + "message": "پیوند منقضی شد" }, "pleaseRestartRegistrationOrTryLoggingIn": { - "message": "Please restart registration or try logging in." + "message": "لطفاً ثبت نام را مجدداً شروع کنید یا دوباره وارد شوید." }, "youMayAlreadyHaveAnAccount": { - "message": "You may already have an account" + "message": "ممکن است قبلاً حساب کاربری داشته باشید" }, "logOutConfirmation": { "message": "آیا مطمئنید که می‌خواهید خارج شوید؟" @@ -905,7 +911,7 @@ "message": "خیر" }, "location": { - "message": "Location" + "message": "موقعیت" }, "unexpectedError": { "message": "یک خطای غیر منتظره رخ داده است." @@ -920,10 +926,10 @@ "message": "ورود دو مرحله ای باعث می‌شود که حساب کاربری شما با استفاده از یک دستگاه دیگر مانند کلید امنیتی، برنامه احراز هویت، پیامک، تماس تلفنی و یا ایمیل، اعتبار خود را با ایمنی بیشتر اثبات کند. ورود دو مرحله ای می تواند در bitwarden.com فعال شود. آیا می‌خواهید از سایت بازدید کنید؟" }, "twoStepLoginConfirmationContent": { - "message": "Make your account more secure by setting up two-step login in the Bitwarden web app." + "message": "با راه‌اندازی ورود دو مرحله‌ای در برنامه وب Bitwarden، حساب کاربری خود را ایمن‌تر کنید." }, "twoStepLoginConfirmationTitle": { - "message": "Continue to web app?" + "message": "با برنامه وب ادامه می‌دهید؟" }, "editedFolder": { "message": "پوشه ذخیره شد" @@ -1010,16 +1016,16 @@ "message": "درخواست افزودن ورود به سیستم" }, "vaultSaveOptionsTitle": { - "message": "Save to vault options" + "message": "گزینه‌های ذخیره در گاوصندوق" }, "addLoginNotificationDesc": { "message": "در صورتی که موردی در گاوصندوق شما یافت نشد، درخواست افزودن کنید." }, "addLoginNotificationDescAlt": { - "message": "Ask to add an item if one isn't found in your vault. Applies to all logged in accounts." + "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,86 @@ "notificationAddSave": { "message": "ذخیره" }, - "loginSaveSuccessDetails": { - "message": "$USERNAME$ saved to 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$ updated in Bitwarden.", + "notificationNewItemAria": { + "message": "مورد جدید، در پنجره جدید باز می‌شود", + "description": "Aria label for the new item button in notification bar confirmation message when error is prompted" + }, + "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": { - "content": "$1" + "label": { + "content": "$1", + "example": "Login" } - }, - "description": "Shown to user after login is updated." + } + }, + "notificationLoginSaveConfirmation": { + "message": "در Bitwarden ذخیره شد.", + "description": "Shown to user after item is saved." + }, + "notificationLoginUpdatedConfirmation": { + "message": "در Bitwarden به‌روزرسانی شد.", + "description": "Shown to user after item is updated." + }, + "selectItemAriaLabel": { + "message": "انتخاب $ITEMTYPE$، $ITEMNAME$", + "description": "Used by screen readers. $1 is the item type (like vault or folder), $2 is the selected item name.", + "placeholders": { + "itemType": { + "content": "$1" + }, + "itemName": { + "content": "$2" + } + } }, "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?", + "unlockToSave": { + "message": "برای ذخیره این ورود، قفل را باز کنید", + "description": "User prompt to take action in order to save the login they just entered." + }, + "saveLogin": { + "message": "ذخیره ورود", "description": "Prompt asking the user if they want to save their login details." }, - "updateLoginPrompt": { - "message": "Update existing login?", + "updateLogin": { + "message": "به‌روزرسانی ورود به سیستم موجود", "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 +1150,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 +1162,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": { @@ -1138,13 +1180,13 @@ "message": "هنگامی که تغییری در یک وب‌سایت شناسایی شد، درخواست به‌روزرسانی کلمه عبور ورود کن." }, "changedPasswordNotificationDescAlt": { - "message": "Ask to update a login's password when a change is detected on a website. Applies to all logged in accounts." + "message": "هنگامی که تغییری در کلمه عبور یک ورود در وب‌سایت شناسایی شود، درخواست به‌روزرسانی آن را بده. این مورد برای همه حساب‌های وارد شده اعمال می‌شود." }, "enableUsePasskeys": { "message": "برای ذخیره و استفاده از passkey اجازه بگیر" }, "usePasskeysDesc": { - "message": "Ask to save new passkeys or log in with passkeys stored in your vault. Applies to all logged in accounts." + "message": "درخواست ذخیره کلیدهای عبور جدید یا ورود با کلیدهای عبوری که در گاوصندوق شما ذخیره شده‌اند. این مورد برای همه حساب‌های وارد شده اعمال می‌شود." }, "notificationChangeDesc": { "message": "آیا مایل به به‌روزرسانی این کلمه عبور در Bitwarden هستید؟" @@ -1168,7 +1210,7 @@ "message": "از یک کلیک ثانویه برای دسترسی به تولید کلمه عبور و ورودهای منطبق برای وب سایت استفاده کن." }, "contextMenuItemDescAlt": { - "message": "Use a secondary click to access password generation and matching logins for the website. Applies to all logged in accounts." + "message": "برای دسترسی به تولید کلمه عبور و ورودهای منطبق با وب‌سایت، از کلیک ثانویه استفاده کنید. این مورد برای همه حساب‌های کاربری وارد شده اعمال می‌شود." }, "defaultUriMatchDetection": { "message": "بررسی مطابقت نشانی اینترنتی پیش‌فرض", @@ -1184,7 +1226,7 @@ "message": "تغییر رنگ پوسته برنامه." }, "themeDescAlt": { - "message": "Change the application's color theme. Applies to all logged in accounts." + "message": "تغییر تم رنگی برنامه. این مورد برای همه حساب‌های کاربری وارد شده اعمال می‌شود." }, "dark": { "message": "تاریک", @@ -1195,7 +1237,7 @@ "description": "Light color" }, "exportFrom": { - "message": "صادرات از" + "message": "برون ریزی از" }, "exportVault": { "message": "برون ریزی گاوصندوق" @@ -1204,35 +1246,35 @@ "message": "فرمت پرونده" }, "fileEncryptedExportWarningDesc": { - "message": "This file export will be password protected and require the file password to decrypt." + "message": "این پرونده برون ریزی با کلمه عبور محافظت می‌شود و برای رمزگشایی به کلمه عبور پرونده نیاز دارد." }, "filePassword": { - "message": "رمز فایل" + "message": "کلمه عبور پرونده" }, "exportPasswordDescription": { - "message": "This password will be used to export and import this file" + "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": "برای رمزگذاری برون ریزی و محدود کردن درون ریزی فقط به حساب کاربری فعلی Bitwarden، از کلید رمزگذاری حساب خود که از نام کاربری و کلمه عبور اصلی حساب شما مشتق شده است استفاده کنید." }, "passwordProtectedOptionDescription": { - "message": "Set a file password to encrypt the export and import it to any Bitwarden account using the password for decryption." + "message": "یک کلمه عبور برای پرونده به‌منظور رمزگذاری تنظیم کنید تا برون ریزی و درون ریزی آن به هر حساب Bitwarden با استفاده از کلمه عبور رمزگشایی شود." }, "exportTypeHeading": { - "message": "نوع صادرات" + "message": "نوع برون ریزی" }, "accountRestricted": { "message": "حساب کاربری محدود شده است" }, "filePasswordAndConfirmFilePasswordDoNotMatch": { - "message": "عدم تطابق \"رمز فایل\" و \"تایید رمز فایل\" با یکدیگر." + "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 +1296,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 for Business به شما اجازه می‌دهد تا با استفاده از یک سازمان، موارد گاوصندوق خود را با دیگران به اشتراک بگذارید. در وب سایت bitwarden.com بیشتر بیاموزید." }, "moveToOrganization": { "message": "انتقال به سازمان" @@ -1312,7 +1354,7 @@ "message": "پرونده" }, "fileToShare": { - "message": "File to share" + "message": "پرونده‌ای برای اشتراک‌گذاری" }, "selectFile": { "message": "ﺍﻧﺘﺨﺎﺏ یک ﭘﺮﻭﻧﺪﻩ" @@ -1323,8 +1365,8 @@ "featureUnavailable": { "message": "ویژگی موجود نیست" }, - "encryptionKeyMigrationRequired": { - "message": "انتقال کلید رمزگذاری مورد نیاز است. لطفاً از طریق گاوصندوق وب وارد شوید تا کلید رمزگذاری خود را به روز کنید." + "legacyEncryptionUnsupported": { + "message": "Legacy encryption is no longer supported. Please contact support to recover your account." }, "premiumMembership": { "message": "عضویت پرمیوم" @@ -1348,13 +1390,13 @@ "message": "۱ گیگابایت فضای ذخیره سازی رمزگذاری شده برای پیوست های پرونده." }, "premiumSignUpEmergency": { - "message": "Emergency access." + "message": "دسترسی اضطراری." }, "premiumSignUpTwoStepOptions": { "message": "گزینه های ورود اضافی دو مرحله ای مانند YubiKey و Duo." }, "ppremiumSignUpReports": { - "message": "گزارش‌های بهداشت رمز عبور، سلامت حساب و نقض داده‌ها برای ایمن نگهداشتن گاوصندوق شما." + "message": "گزارش‌های بهداشت کلمه عبور، سلامت حساب و نقض داده‌ها برای ایمن نگهداشتن گاوصندوق شما." }, "ppremiumSignUpTotp": { "message": "تولید کننده کد تأیید (2FA) از نوع TOTP برای ورودهای در گاوصندوقتان." @@ -1369,7 +1411,7 @@ "message": "خرید پرمیوم" }, "premiumPurchaseAlertV2": { - "message": "You can purchase Premium from your account settings on the Bitwarden web app." + "message": "می‌توانید نسخه پرمیوم را از تنظیمات حساب کاربری خود در اپلیکیشن وب Bitwarden خریداری کنید." }, "premiumCurrentMember": { "message": "شما یک عضو پرمیوم هستید!" @@ -1378,7 +1420,7 @@ "message": "برای حمایتتان از Bitwarden سپاسگزاریم." }, "premiumFeatures": { - "message": "Upgrade to Premium and receive:" + "message": "ارتقا به نسخه پرمیوم و دریافت:" }, "premiumPrice": { "message": "تمامش فقط $PRICE$ در سال!", @@ -1390,7 +1432,7 @@ } }, "premiumPriceV2": { - "message": "All for just $PRICE$ per year!", + "message": "تمامش فقط $PRICE$ در سال!", "placeholders": { "price": { "content": "$1", @@ -1417,10 +1459,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 +1474,29 @@ } }, "dontAskAgainOnThisDeviceFor30Days": { - "message": "Don't ask again on this device for 30 days" + "message": "در این دستگاه به مدت ۳۰ روز دوباره نپرس" }, "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 +1511,7 @@ "message": "گزینه‌های ورود دو مرحله‌ای" }, "selectTwoStepLoginMethod": { - "message": "Select two-step login method" + "message": "انتخاب ورود دو مرحله‌ای" }, "recoveryCodeDesc": { "message": "دسترسی به تمامی ارائه‌دهندگان دو مرحله‌ای را از دست داده‌اید؟ از کد بازیابی خود برای غیرفعال‌سازی ارائه‌دهندگان دو مرحله‌ای از حسابتان استفاده کنید." @@ -1481,17 +1523,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": "کلید امنیت Yubico OTP" }, "yubiKeyDesc": { "message": "از یک YubiKey برای دسترسی به حسابتان استفاده کنید. همراه با دستگاه‌های YubiKey 4 ،4 Nano ،NEO کار می‌کند." }, "duoDescV2": { - "message": "Enter a code generated by Duo Security.", + "message": "کدی را وارد کنید که توسط Duo Security تولید شده است.", "description": "'Duo Security' and 'Duo Mobile' are product names and should not be translated." }, "duoOrganizationDesc": { @@ -1508,19 +1550,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": "نشانی اینترنتی پایه نصب Bitwarden خود را که به‌صورت داخلی میزبانی شده مشخص کنید.\nمثال: https://bitwarden.company.com" }, "selfHostedCustomEnvHeader": { - "message": "For advanced configuration, you can specify the base URL of each service independently." + "message": "برای پیکربندی پیشرفته، می‌توانید نشانی اینترنتی پایه هر سرویس را به‌صورت مستقل مشخص کنید." }, "selfHostedEnvFormInvalid": { - "message": "You must add either the base Server URL or at least one custom environment." + "message": "شما باید یا نشانی اینترنتی پایه سرور را اضافه کنید، یا حداقل یک محیط سفارشی تعریف کنید." }, "customEnvironment": { "message": "محیط سفارشی" @@ -1529,7 +1571,7 @@ "message": "نشانی اینترنتی سرور" }, "selfHostBaseUrl": { - "message": "Self-host server URL", + "message": "نشانی اینترنتی سرور خود میزبان", "description": "Label for field requesting a self-hosted integration service URL" }, "apiUrl": { @@ -1551,29 +1593,47 @@ "message": "نشانی‌های اینترنتی محیط ذخیره شد" }, "showAutoFillMenuOnFormFields": { - "message": "Show autofill menu on form fields", + "message": "نمایش منوی پر کردن خودکار روی فیلدهای فرم", "description": "Represents the message for allowing the user to enable the autofill overlay" }, "autofillSuggestionsSectionTitle": { - "message": "Autofill suggestions" + "message": "پیشنهادهای پر کردن خودکار" + }, + "autofillSpotlightTitle": { + "message": "یافتن آسان پیشنهادهای پر کردن خودکار" + }, + "autofillSpotlightDesc": { + "message": "تنظیمات پر کردن خودکار مرورگر خود را غیرفعال کنید تا با Bitwarden تداخل نداشته باشد." + }, + "turnOffBrowserAutofill": { + "message": "پر کردن خودکار $BROWSER$ را غیرفعال کنید", + "placeholders": { + "browser": { + "content": "$1", + "example": "Chrome" + } + } + }, + "turnOffAutofill": { + "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": "Turn off your browser's built in password manager settings to avoid conflicts." + "message": "تنظیمات مدیر کلمه عبور داخلی مرورگر خود را غیرفعال کنید تا از بروز تداخل جلوگیری شود." }, "turnOffBrowserBuiltInPasswordManagerSettingsLink": { "message": "ویرایش تنظیمات مرورگر." @@ -1583,15 +1643,15 @@ "description": "Overlay setting select option for disabling autofill overlay" }, "autofillOverlayVisibilityOnFieldFocus": { - "message": "When field is selected (on focus)", + "message": "وقتی فیلد انتخاب شد (در حالت فوکوس)", "description": "Overlay appearance select option for showing the field on focus of the input element" }, "autofillOverlayVisibilityOnButtonClick": { - "message": "When autofill icon is selected", + "message": "وقتی آیکون پر کردن خودکار انتخاب شود", "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 +1663,7 @@ "message": "وب‌سایت‌های در معرض خطر یا نامعتبر می‌توانند از پر کردن خودکار در بارگذاری صفحه سوء استفاده کنند." }, "learnMoreAboutAutofillOnPageLoadLinkText": { - "message": "Learn more about risks" + "message": "درباره‌ خطراتش بیش‌تر بدانید" }, "learnMoreAboutAutofill": { "message": "درباره پر کردن خودکار بیشتر بدانید" @@ -1633,13 +1693,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 +1723,7 @@ "message": "برای مرتب‌سازی بکشید" }, "dragToReorder": { - "message": "Drag to reorder" + "message": "برای تغییر ترتیب، بکشید و رها کنید" }, "cfTypeText": { "message": "متن" @@ -1675,7 +1735,7 @@ "message": "منطقی" }, "cfTypeCheckbox": { - "message": "Checkbox" + "message": "کادر انتخاب" }, "cfTypeLinked": { "message": "پیوند شده", @@ -1698,7 +1758,7 @@ "message": "یک تصویر قابل تشخیص در کنار هر ورود نشان دهید." }, "faviconDescAlt": { - "message": "Show a recognizable image next to each login. Applies to all logged in accounts." + "message": "نمایش تصویر قابل تشخیص کنار هر ورود به سیستم. برای تمام حساب‌های کاربری وارد شده اعمال می‌شود." }, "enableBadgeCounter": { "message": "نمایش شمارنده نشان" @@ -1860,7 +1920,7 @@ "message": "هویت" }, "typeSshKey": { - "message": "SSH key" + "message": "کلید SSH" }, "newItemHeader": { "message": "$TYPE$ جدید", @@ -1881,7 +1941,7 @@ } }, "viewItemHeader": { - "message": "View $TYPE$", + "message": "مشاهده $TYPE$", "placeholders": { "type": { "content": "$1", @@ -1893,13 +1953,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 +1968,7 @@ "message": "مجموعه‌ها" }, "nCollections": { - "message": "$COUNT$ collections", + "message": "$COUNT$ مجموعه‌ها", "placeholders": { "count": { "content": "$1", @@ -1938,7 +1998,7 @@ "message": "یادداشت‌های امن" }, "sshKeys": { - "message": "SSH Keys" + "message": "کلید‌‌های SSH" }, "clear": { "message": "پاک کردن", @@ -1964,7 +2024,7 @@ "description": "Domain name. Ex. website.com" }, "baseDomainOptionRecommended": { - "message": "Base domain (recommended)", + "message": "دامنه پایه (پیشنهادی)", "description": "Domain name. Ex. website.com" }, "domainName": { @@ -2018,13 +2078,13 @@ "message": "هیچ کلمه عبوری برای فهرست کردن وجود ندارد." }, "clearHistory": { - "message": "Clear history" + "message": "پاک کردن تاریخچه" }, "nothingToShow": { - "message": "Nothing to show" + "message": "چیزی برای نشان دادن موجود نیست" }, "nothingGeneratedRecently": { - "message": "You haven't generated anything recently" + "message": "شما اخیراً چیزی تولید نکرده‌اید" }, "remove": { "message": "حذف" @@ -2085,16 +2145,16 @@ "message": "باز کردن با پین" }, "setYourPinTitle": { - "message": "Set PIN" + "message": "تنظیم کد پین" }, "setYourPinButton": { - "message": "Set PIN" + "message": "تنظیم کد پین" }, "setYourPinCode": { "message": "کد پین خود را برای باز کردن Bitwarden تنظیم کنید. اگر به طور کامل از برنامه خارج شوید، تنظیمات پین شما از بین می‌رود." }, "setYourPinCode1": { - "message": "Your PIN will be used to unlock Bitwarden instead of your master password. Your PIN will reset if you ever fully log out of Bitwarden." + "message": "کد پین شما برای باز کردن Bitwarden به جای کلمه عبور اصلی استفاده خواهد شد. در صورتی که کاملاً از Bitwarden خارج شوید، کد پین شما ریست خواهد شد." }, "pinRequired": { "message": "کد پین الزامیست." @@ -2103,13 +2163,13 @@ "message": "کد پین معتبر نیست." }, "tooManyInvalidPinEntryAttemptsLoggingOut": { - "message": "Too many invalid PIN entry attempts. Logging out." + "message": "تعداد تلاش‌های ناموفق پین زیاد شد. خارج می‌شوید." }, "unlockWithBiometrics": { "message": "با استفاده از بیومتریک باز کنید" }, "unlockWithMasterPassword": { - "message": "Unlock with master password" + "message": "باز کردن قفل با کلمه عبور اصلی" }, "awaitDesktop": { "message": "در انتظار تأیید از دسکتاپ" @@ -2121,7 +2181,7 @@ "message": "در زمان شروع مجدد، با کلمه عبور اصلی قفل کن" }, "lockWithMasterPassOnRestart1": { - "message": "Require master password on browser restart" + "message": "در زمان شروع مجدد، کلمه عبور اصلی نیاز است" }, "selectOneCollection": { "message": "شما باید حداقل یک مجموعه را انتخاب کنید." @@ -2133,48 +2193,42 @@ "message": "شبیه سازی" }, "passwordGenerator": { - "message": "Password generator" + "message": "تولید کننده کلمه عبور" }, "usernameGenerator": { - "message": "Username generator" + "message": "تولید کننده نام کاربری" }, "useThisEmail": { - "message": "Use this email" + "message": "از این ایمیل استفاده شود" }, "useThisPassword": { - "message": "Use this password" + "message": "از این کلمه عبور استفاده کن" + }, + "useThisPassphrase": { + "message": "Use this passphrase" }, "useThisUsername": { - "message": "Use this username" + "message": "از این نام کاربری استفاده کن" }, "securePasswordGenerated": { - "message": "Secure password generated! Don't forget to also update your password on the website." + "message": "کلمه عبور ایمن ساخته شد! فراموش نکنید کلمه عبور خود را در وب‌سایت نیز به‌روزرسانی کنید." }, "useGeneratorHelpTextPartOne": { - "message": "Use the generator", + "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": "to create a strong unique password", + "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'" }, "vaultCustomization": { - "message": "Vault customization" + "message": "سفارشی‌سازی گاوصندوق" }, "vaultTimeoutAction": { "message": "عمل متوقف شدن گاو‌صندوق" }, "vaultTimeoutAction1": { - "message": "Timeout action" - }, - "newCustomizationOptionsCalloutTitle": { - "message": "New customization options" - }, - "newCustomizationOptionsCalloutContent": { - "message": "Customize your vault experience with quick copy actions, compact mode, and more!" - }, - "newCustomizationOptionsCalloutLink": { - "message": "View all Appearance settings" + "message": "اقدام در صورت پایان زمان" }, "lock": { "message": "قفل", @@ -2203,7 +2257,7 @@ "message": "مورد بازیابی شد" }, "alreadyHaveAccount": { - "message": "Already have an account?" + "message": "در حال حاضر حساب کاربری دارید؟" }, "vaultTimeoutLogOutConfirmation": { "message": "خروج از سیستم، تمام دسترسی ها به گاو‌صندوق شما را از بین می‌برد و نیاز به احراز هویت آنلاین پس از مدت زمان توقف دارد. آیا مطمئن هستید که می‌خواهید از این تنظیمات استفاده کنید؟" @@ -2296,7 +2350,7 @@ "message": "کلمه عبور اصلی جدید شما از شرایط سیاست پیروی نمی‌کند." }, "receiveMarketingEmailsV2": { - "message": "Get advice, announcements, and research opportunities from Bitwarden in your inbox." + "message": "پیشنهادها، اعلان‌ها و فرصت‌های تحقیقاتی Bitwarden را در صندوق ورودی خود دریافت کنید." }, "unsubscribe": { "message": "لغو اشتراک" @@ -2323,7 +2377,7 @@ "message": "سیاست حفظ حریم خصوصی" }, "yourNewPasswordCannotBeTheSameAsYourCurrentPassword": { - "message": "Your new password cannot be the same as your current password." + "message": "کلمه عبور جدید شما نمی‌تواند با کلمه عبور فعلی‌تان یکسان باشد." }, "hintEqualsPassword": { "message": "اشاره به کلمه عبور شما نمی‌تواند همان کلمه عبور شما باشد." @@ -2332,10 +2386,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": "تأیید همگام‌سازی دسکتاپ" @@ -2374,10 +2428,10 @@ "message": "عدم مطابقت حساب کاربری" }, "nativeMessagingWrongUserKeyTitle": { - "message": "Biometric key missmatch" + "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": "بیومتریک برپا نشده" @@ -2395,13 +2449,13 @@ "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": "زیست‌سنجی ناموفق بود" @@ -2428,17 +2482,17 @@ "message": "سیاست سازمانی بر تنظیمات مالکیت شما تأثیر می‌گذارد." }, "personalOwnershipPolicyInEffectImports": { - "message": "An organization policy has blocked importing items into your individual vault." + "message": "یک سیاست سازمانی، درون ریزی موارد به گاوصندوق فردی شما را مسدود کرده است." }, "domainsTitle": { - "message": "Domains", + "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": "دامنه های مستثنی" @@ -2447,22 +2501,22 @@ "message": "Bitwarden برای ذخیره جزئیات ورود به سیستم این دامنه ها سوال نمی‌کند. برای اینکه تغییرات اعمال شود باید صفحه را تازه کنید." }, "excludedDomainsDescAlt": { - "message": "Bitwarden will not ask to save login details for these domains for all logged in accounts. You must refresh the page for changes to take effect." + "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", @@ -2471,10 +2525,10 @@ } }, "atRiskPasswords": { - "message": "At-risk passwords" + "message": "کلمات عبور در معرض خطر" }, "atRiskPasswordDescSingleOrg": { - "message": "$ORGANIZATION$ is requesting you change one password because it is at-risk.", + "message": "$ORGANIZATION$ از شما درخواست کرده یک کلمه عبور را به دلیل در معرض خطر بودن تغییر دهید.", "placeholders": { "organization": { "content": "$1", @@ -2483,7 +2537,7 @@ } }, "atRiskPasswordsDescSingleOrgPlural": { - "message": "$ORGANIZATION$ is requesting you change the $COUNT$ passwords because they are at-risk.", + "message": "$ORGANIZATION$ از شما درخواست کرده $COUNT$ کلمه عبور را به دلیل در معرض خطر بودن تغییر دهید.", "placeholders": { "organization": { "content": "$1", @@ -2496,7 +2550,7 @@ } }, "atRiskPasswordsDescMultiOrgPlural": { - "message": "Your organizations are requesting you change the $COUNT$ passwords because they are at-risk.", + "message": "سازمان‌های شما از شما درخواست کرده‌اند که $COUNT$ کلمه عبور را به دلیل در معرض خطر بودن تغییر دهید.", "placeholders": { "count": { "content": "$1", @@ -2505,10 +2559,10 @@ } }, "reviewAndChangeAtRiskPassword": { - "message": "Review and change one at-risk password" + "message": "بررسی و تغییر یک کلمه عبور در معرض خطر" }, "reviewAndChangeAtRiskPasswordsPlural": { - "message": "Review and change $COUNT$ at-risk passwords", + "message": "بررسی و تغییر $COUNT$ کلمه عبور در معرض خطر", "placeholders": { "count": { "content": "$1", @@ -2517,52 +2571,52 @@ } }, "changeAtRiskPasswordsFaster": { - "message": "Change at-risk passwords faster" + "message": "تغییر سریع‌تر کلمات عبور در معرض خطر" }, "changeAtRiskPasswordsFasterDesc": { - "message": "Update your settings so you can quickly autofill your passwords and generate new ones" + "message": "تنظیمات خود را به‌روزرسانی کنید تا بتوانید به‌سرعت کلمات عبور خود را به‌صورت خودکار وارد کرده و کلمات جدید تولید کنید" }, "reviewAtRiskLogins": { - "message": "Review at-risk logins" + "message": "بررسی ورودهای در معرض خطر" }, "reviewAtRiskPasswords": { - "message": "Review at-risk passwords" + "message": "بررسی کلمات عبور در معرض خطر" }, "reviewAtRiskLoginsSlideDesc": { - "message": "Your organization passwords are at-risk because they are weak, reused, and/or exposed.", + "message": "کلمات عبور سازمان شما در معرض خطر هستند زیرا ضعیف، تکراری و یا افشا شده‌اند.", "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": "تصویری از فهرست ورودهایی که در معرض خطر هستند." }, "generatePasswordSlideDesc": { - "message": "Quickly generate a strong, unique password with the Bitwarden autofill menu on the at-risk site.", + "message": "با استفاده از منوی پر کردن خودکار Bitwarden در سایت در معرض خطر، به‌سرعت یک کلمه عبور قوی و منحصر به فرد تولید کنید.", "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": "تصویری از منوی پر کردن خودکار Bitwarden که یک کلمه عبور تولید شده را نمایش می‌دهد." }, "updateInBitwarden": { - "message": "Update in Bitwarden" + "message": "به‌روزرسانی در Bitwarden" }, "updateInBitwardenSlideDesc": { - "message": "Bitwarden will then prompt you to update the password in the password manager.", + "message": "سپس Bitwarden از شما می‌خواهد کلمه عبور را در مدیریت کلمه عبور به‌روزرسانی کنید.", "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": "تصویری از اعلان Bitwarden که از کاربر می‌خواهد ورود خود را به‌روزرسانی کند." }, "turnOnAutofill": { - "message": "Turn on autofill" + "message": "پر کردن خودکار را فعال کنید" }, "turnedOnAutofill": { - "message": "Turned on autofill" + "message": "پر کردن خودکار فعال شد" }, "dismiss": { - "message": "Dismiss" + "message": "نادیده گرفتن" }, "websiteItemLabel": { - "message": "Website $number$ (URI)", + "message": "وب‌سایت $number$ (نشانی اینترنتی)", "placeholders": { "number": { "content": "$1", @@ -2580,20 +2634,20 @@ } }, "blockedDomainsSavedSuccess": { - "message": "Blocked domain changes saved" + "message": "تغییرات دامنه مسدود شده ذخیره شد" }, "excludedDomainsSavedSuccess": { - "message": "Excluded domain changes saved" + "message": "تغییرات دامنه مستثنی شده ذخیره شد" }, "limitSendViews": { - "message": "Limit views" + "message": "محدود کردن نمایش‌ها" }, "limitSendViewsHint": { - "message": "No one can view this Send after the limit is reached.", + "message": "هیچ‌کس نمی‌تواند این را پس از رسیدن به محدودیت مشاهده کند.", "description": "Displayed under the limit views field on Send" }, "limitSendViewsCount": { - "message": "$ACCESSCOUNT$ views left", + "message": "$ACCESSCOUNT$ بازدید باقی مانده", "description": "Displayed under the limit views field on Send", "placeholders": { "accessCount": { @@ -2607,14 +2661,14 @@ "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "sendDetails": { - "message": "Send details", + "message": "جزئیات ارسال", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "sendTypeText": { "message": "متن" }, "sendTypeTextToShare": { - "message": "Text to share" + "message": "متن برای اشتراک گذاری" }, "sendTypeFile": { "message": "پرونده" @@ -2623,8 +2677,12 @@ "message": "همه ارسال ها", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, + "maxAccessCountReached": { + "message": "Max access count reached", + "description": "This text will be displayed after a Send has been accessed the maximum amount of times." + }, "hideTextByDefault": { - "message": "Hide text by default" + "message": "متن را به‌صورت پیش‌فرض مخفی کن" }, "expired": { "message": "منقضی شده" @@ -2633,7 +2691,7 @@ "message": "محافظت ‌شده با کلمه عبور" }, "copyLink": { - "message": "Copy link" + "message": "کپی پیوند" }, "copySendLink": { "message": "پیوند ارسال را کپی کن", @@ -2671,7 +2729,7 @@ "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "deleteSendPermanentConfirmation": { - "message": "Are you sure you want to permanently delete this Send?", + "message": "مطمئن هستید می‌خواهید این ارسال را برای همیشه پاک کنید؟", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "editSend": { @@ -2682,7 +2740,7 @@ "message": "تاریخ حذف" }, "deletionDateDescV2": { - "message": "The Send will be permanently deleted on this date.", + "message": "ارسال در این تاریخ به‌طور دائمی حذف خواهد شد.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "expirationDate": { @@ -2704,7 +2762,7 @@ "message": "سفارشی" }, "sendPasswordDescV3": { - "message": "Add an optional password for recipients to access this Send.", + "message": "یک کلمه عبور اختیاری برای دریافت‌کنندگان اضافه کنید تا بتوانند به این ارسال دسترسی داشته باشند.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "createSend": { @@ -2727,15 +2785,15 @@ "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 +2803,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,7 +2817,7 @@ } }, "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": { @@ -2767,11 +2825,11 @@ "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": { @@ -2784,7 +2842,7 @@ "message": "برای انتخاب پرونده ای با استفاده از Safari، با کلیک روی این بنر پنجره جدیدی باز کنید." }, "popOut": { - "message": "Pop out" + "message": "باز کردن در پنجره جداگانه" }, "sendFileCalloutHeader": { "message": "قبل از اینکه شروع کنی" @@ -2805,7 +2863,7 @@ "message": "هنگام ذخیره حذف و تاریخ انقضاء شما خطایی روی داد." }, "hideYourEmail": { - "message": "Hide your email address from viewers." + "message": "آدرس ایمیل خود را از بینندگان مخفی کنید." }, "passwordPrompt": { "message": "درخواست مجدد کلمه عبور اصلی" @@ -2820,7 +2878,7 @@ "message": "تأیید ایمیل لازم است" }, "emailVerifiedV2": { - "message": "Email verified" + "message": "ایمیل تأیید شد" }, "emailVerificationRequiredDesc": { "message": "برای استفاده از این ویژگی باید ایمیل خود را تأیید کنید. می‌توانید ایمیل خود را در گاوصندوق وب تأیید کنید." @@ -2838,7 +2896,7 @@ "message": "کلمه عبور اصلی شما با یک یا چند سیاست سازمان‌تان مطابقت ندارد. برای دسترسی به گاوصندوق، باید همین حالا کلمه عبور اصلی خود را به‌روز کنید. در صورت ادامه، شما از نشست فعلی خود خارج می‌شوید و باید دوباره وارد سیستم شوید. نشست فعال در دستگاه های دیگر ممکن است تا یک ساعت همچنان فعال باقی بمانند." }, "tdeDisabledMasterPasswordRequired": { - "message": "Your organization has disabled trusted device encryption. Please set a master password to access your vault." + "message": "سازمان شما رمزگذاری دستگاه‌های مورد اعتماد را غیرفعال کرده است. لطفاً برای دسترسی به گاوصندوق خود یک کلمه عبور اصلی تنظیم کنید." }, "resetPasswordPolicyAutoEnroll": { "message": "ثبت نام خودکار" @@ -2854,15 +2912,15 @@ "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 +2939,7 @@ "message": "دقیقه" }, "vaultTimeoutPolicyAffectingOptions": { - "message": "Enterprise policy requirements have been applied to your timeout options" + "message": "نیازمندی‌های سیاست سازمانی بر گزینه‌های زمان پایان نشست شما اعمال شده است" }, "vaultTimeoutPolicyInEffect": { "message": "سیاست‌های سازمانتان بر مهلت زمانی گاوصندوق شما تأثیر می‌گذارد. حداکثر زمان مجاز گاوصندوق $HOURS$ ساعت و $MINUTES$ دقیقه است", @@ -2897,7 +2955,7 @@ } }, "vaultTimeoutPolicyInEffect1": { - "message": "$HOURS$ hour(s) and $MINUTES$ minute(s) maximum.", + "message": "حداکثر $HOURS$ ساعت و $MINUTES$ دقیقه.", "placeholders": { "hours": { "content": "$1", @@ -2910,7 +2968,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", @@ -2963,14 +3021,14 @@ "copyCustomFieldNameNotUnique": { "message": "شناسه منحصر به فردی یافت نشد." }, - "convertOrganizationEncryptionDesc": { - "message": "$ORGANIZATION$ در حال استفاده از SSO با یک سرور کلید خود میزبان است. برای ورود اعضای این سازمان دیگر نیازی به کلمه عبور اصلی نیست.", - "placeholders": { - "organization": { - "content": "$1", - "example": "My Org Name" - } - } + "removeMasterPasswordForOrganizationUserKeyConnector": { + "message": "برای اعضای سازمان زیر، کلمه عبور اصلی دیگر لازم نیست. لطفاً دامنه زیر را با مدیر سازمان خود تأیید کنید." + }, + "organizationName": { + "message": "نام سازمان" + }, + "keyConnectorDomain": { + "message": "دامنه رابط کلید" }, "leaveOrganization": { "message": "ترک سازمان" @@ -3006,7 +3064,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 +3073,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 +3088,27 @@ "message": "خطا" }, "decryptionError": { - "message": "Decryption error" + "message": "خطای رمزگشایی" }, "couldNotDecryptVaultItemsBelow": { - "message": "Bitwarden could not decrypt the vault item(s) listed below." + "message": "Bitwarden نتوانست مورد(های) گاوصندوق فهرست شده زیر را رمزگشایی کند." }, "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 +3122,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 +3132,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": { @@ -3115,15 +3173,15 @@ "message": "یک نام مستعار ایمیل با یک سرویس ارسال خارجی ایجاد کنید." }, "forwarderDomainName": { - "message": "Email domain", + "message": "دامنه ایمیل", "description": "Labels the domain name email forwarder service option" }, "forwarderDomainNameHint": { - "message": "Choose a domain that is supported by the selected service", + "message": "دامنه‌ای را انتخاب کنید که توسط سرویس انتخاب شده پشتیبانی می‌شود", "description": "Guidance provided for email forwarding services that support multiple email domains." }, "forwarderError": { - "message": "$SERVICENAME$ error: $ERRORMESSAGE$", + "message": "$SERVICENAME$ خطا: $ERRORMESSAGE$", "description": "Reports an error returned by a forwarding service to the user.", "placeholders": { "servicename": { @@ -3137,11 +3195,11 @@ } }, "forwarderGeneratedBy": { - "message": "Generated by Bitwarden.", + "message": "تولید شده توسط Bitwarden.", "description": "Displayed with the address on the forwarding service's configuration screen." }, "forwarderGeneratedByWithWebsite": { - "message": "Website: $WEBSITE$. Generated by Bitwarden.", + "message": "وب‌سایت: $WEBSITE$. تولید شده توسط Bitwarden.", "description": "Displayed with the address on the forwarding service's configuration screen.", "placeholders": { "WEBSITE": { @@ -3151,7 +3209,7 @@ } }, "forwaderInvalidToken": { - "message": "Invalid $SERVICENAME$ API token", + "message": "توکن API نامعتبر برای $SERVICENAME$", "description": "Displayed when the user's API token is empty or rejected by the forwarding service.", "placeholders": { "servicename": { @@ -3161,7 +3219,7 @@ } }, "forwaderInvalidTokenWithMessage": { - "message": "Invalid $SERVICENAME$ API token: $ERRORMESSAGE$", + "message": "توکن API نامعتبر برای $SERVICENAME$: $ERRORMESSAGE$", "description": "Displayed when the user's API token is rejected by the forwarding service with an error message.", "placeholders": { "servicename": { @@ -3175,7 +3233,7 @@ } }, "forwaderInvalidOperation": { - "message": "$SERVICENAME$ refused your request. Please contact your service provider for assistance.", + "message": "$SERVICENAME$ درخواست شما را رد کرد. لطفاً برای دریافت کمک با ارائه‌دهنده سرویس خود تماس بگیرید.", "description": "Displayed when the user is forbidden from using the API by the forwarding service.", "placeholders": { "servicename": { @@ -3185,7 +3243,7 @@ } }, "forwaderInvalidOperationWithMessage": { - "message": "$SERVICENAME$ refused your request: $ERRORMESSAGE$", + "message": "$SERVICENAME$ درخواست شما را رد کرد: $ERRORMESSAGE$", "description": "Displayed when the user is forbidden from using the API by the forwarding service with an error message.", "placeholders": { "servicename": { @@ -3199,7 +3257,7 @@ } }, "forwarderNoAccountId": { - "message": "Unable to obtain $SERVICENAME$ masked email account ID.", + "message": "دریافت شناسه حساب ایمیل ماسک شده از $SERVICENAME$ امکان‌پذیر نیست.", "description": "Displayed when the forwarding service fails to return an account ID.", "placeholders": { "servicename": { @@ -3239,7 +3297,7 @@ } }, "forwarderUnknownForwarder": { - "message": "Unknown forwarder: '$SERVICENAME$'.", + "message": "فرواردکننده ناشناخته: $SERVICENAME$.", "description": "Displayed when the forwarding service is not supported.", "placeholders": { "servicename": { @@ -3328,34 +3386,34 @@ "message": "ارسال مجدد اعلان" }, "viewAllLogInOptions": { - "message": "View all log in options" + "message": "مشاهده همه گزینه‌های ورود به سیستم" }, "notificationSentDevice": { "message": "یک اعلان به دستگاه شما ارسال شده است." }, "notificationSentDevicePart1": { - "message": "Unlock Bitwarden on your device or on the" + "message": "قفل Bitwarden را در دستگاه خود یا در... باز کنید" }, "notificationSentDeviceAnchor": { - "message": "web app" + "message": "برنامه وب" }, "notificationSentDevicePart2": { - "message": "Make sure the Fingerprint phrase matches the one below before approving." + "message": "اطمینان حاصل کنید که عبارت اثر انگشت با عبارت زیر مطابقت دارد قبل از تأیید." }, "aNotificationWasSentToYourDevice": { - "message": "A notification was sent to your device" + "message": "یک اعلان به دستگاه شما ارسال شده است" }, "youWillBeNotifiedOnceTheRequestIsApproved": { - "message": "You will be notified once the request is approved" + "message": "به‌محض تأیید درخواست، به شما اطلاع داده خواهد شد" }, "needAnotherOptionV1": { - "message": "Need another option?" + "message": "به گزینه دیگری نیاز دارید؟" }, "loginInitiated": { "message": "ورود به سیستم آغاز شد" }, "logInRequestSent": { - "message": "Request sent" + "message": "درخواست ارسال شد" }, "exposedMasterPassword": { "message": "کلمه عبور اصلی افشا شده" @@ -3394,7 +3452,7 @@ "message": "نحوه پر کردن خودکار" }, "autofillSelectInfoWithCommand": { - "message": "Select an item from this screen, use the shortcut $COMMAND$, or explore other options in settings.", + "message": "یک مورد را از این صفحه انتخاب کنید، از میان‌بر $COMMAND$ استفاده کنید، یا گزینه‌های دیگر را در تنظیمات بررسی کنید.", "placeholders": { "command": { "content": "$1", @@ -3403,7 +3461,7 @@ } }, "autofillSelectInfoWithoutCommand": { - "message": "Select an item from this screen, or explore other options in settings." + "message": "یک مورد را از این صفحه انتخاب کنید یا گزینه‌های دیگر را در تنظیمات بررسی کنید." }, "gotIt": { "message": "متوجه شدم" @@ -3412,22 +3470,22 @@ "message": "تنظیمات پر کردن خودکار" }, "autofillKeyboardShortcutSectionTitle": { - "message": "Autofill shortcut" + "message": "میان‌بر پرکردن خودکار" }, "autofillKeyboardShortcutUpdateLabel": { - "message": "Change shortcut" + "message": "تغییر میان‌بر" }, "autofillKeyboardManagerShortcutsLabel": { - "message": "Manage shortcuts" + "message": "مدیریت میان‌برها" }, "autofillShortcut": { "message": "میانبر صفحه کلید پر کردن خودکار" }, "autofillLoginShortcutNotSet": { - "message": "The autofill login shortcut is not set. Change this in the browser's settings." + "message": "میان‌بر ورود خودکار تنظیم نشده است. این مورد را در تنظیمات مرورگر تغییر دهید." }, "autofillLoginShortcutText": { - "message": "The autofill login shortcut is $COMMAND$. Manage all shortcuts in the browser's settings.", + "message": "میان‌بر ورود خودکار $COMMAND$ است. همه میان‌برها را می‌توانید در تنظیمات مرورگر مدیریت کنید.", "placeholders": { "command": { "content": "$1", @@ -3448,16 +3506,16 @@ "message": "در پنجره جدید باز می‌شود" }, "rememberThisDeviceToMakeFutureLoginsSeamless": { - "message": "Remember this device to make future logins seamless" + "message": "این دستگاه را به خاطر بسپار تا ورودهای بعدی بدون مشکل انجام شود" }, "deviceApprovalRequired": { "message": "تأیید دستگاه لازم است. یک روش تأیید انتخاب کنید:" }, "deviceApprovalRequiredV2": { - "message": "Device approval required" + "message": "تأیید دستگاه لازم است" }, "selectAnApprovalOptionBelow": { - "message": "Select an approval option below" + "message": "یکی از گزینه‌های تأیید زیر را انتخاب کنید" }, "rememberThisDevice": { "message": "این دستگاه را به خاطر بسپار" @@ -3487,13 +3545,13 @@ "message": "و به ساختن حساب‌تان ادامه دهید." }, "noEmail": { - "message": "No email?" + "message": "ایمیلی دریافت نکردید؟" }, "goBack": { - "message": "Go back" + "message": "بازگشت به عقب" }, "toEditYourEmailAddress": { - "message": "to edit your email address." + "message": "برای ویرایش آدرس ایمیل خود." }, "eu": { "message": "اروپا", @@ -3527,17 +3585,41 @@ "message": "ایمیل کاربر وجود ندارد" }, "activeUserEmailNotFoundLoggingYouOut": { - "message": "Active user email not found. Logging you out." + "message": "ایمیل کاربر فعال پیدا نشد. در حال خارج کردن شما از سیستم هستیم." }, "deviceTrusted": { "message": "دستگاه مورد اعتماد است" }, - "sendsNoItemsTitle": { - "message": "No active Sends", + "trustOrganization": { + "message": "اعتماد به سازمان" + }, + "trust": { + "message": "اطمینان" + }, + "doNotTrust": { + "message": "اعتماد نکنید" + }, + "organizationNotTrusted": { + "message": "سازمان مورد اعتماد نیست" + }, + "emergencyAccessTrustWarning": { + "message": "برای امنیت حساب کاربری شما، فقط در صورتی تأیید کنید که دسترسی اضطراری به این کاربر داده‌اید و اثر انگشت او با آنچه در حسابش نمایش داده شده، مطابقت دارد" + }, + "orgTrustWarning": { + "message": "برای امنیت حساب کاربری شما، فقط در صورتی ادامه دهید که عضو این سازمان باشید، بازیابی حساب کاربری را فعال کرده باشید و اثر انگشت نمایش داده شده در زیر با اثر انگشت سازمان مطابقت داشته باشد." + }, + "orgTrustWarning1": { + "message": "این سازمان دارای سیاست سازمانی است که شما را در بازیابی حساب کاربری ثبت‌نام می‌کند. ثبت‌نام به مدیران سازمان اجازه می‌دهد کلمه عبور شما را تغییر دهند. فقط در صورتی ادامه دهید که این سازمان را می‌شناسید و عبارت اثر انگشت نمایش داده شده در زیر با اثر انگشت سازمان مطابقت دارد." + }, + "trustUser": { + "message": "به کاربر اعتماد کنید" + }, + "sendsTitleNoItems": { + "message": "اطلاعات حساس را به‌صورت ایمن ارسال کنید", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, - "sendsNoItemsMessage": { - "message": "Use Send to securely share encrypted information with anyone.", + "sendsBodyNoItems": { + "message": "پرونده‌ها و داده‌های خود را به‌صورت امن با هر کسی، در هر پلتفرمی به اشتراک بگذارید. اطلاعات شما در حین اشتراک‌گذاری به‌طور کامل رمزگذاری انتها به انتها باقی خواهد ماند و میزان افشا محدود می‌شود.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "inputRequired": { @@ -3614,10 +3696,10 @@ } }, "singleFieldNeedsAttention": { - "message": "1 field needs your attention." + "message": "۱ فیلد به توجه شما نیاز دارد." }, "multipleFieldsNeedAttention": { - "message": "$COUNT$ fields need your attention.", + "message": "فیلدهای $COUNT$ به توجه شما نیاز دارند.", "placeholders": { "count": { "content": "$1", @@ -3672,53 +3754,53 @@ "description": "Message appearing below the autofill on load message when master password reprompt is set for a vault item." }, "toggleSideNavigation": { - "message": "Toggle side navigation" + "message": "تغییر وضعیت ناوبری کناری" }, "skipToContent": { - "message": "Skip to content" + "message": "پرش به محتوا" }, "bitwardenOverlayButton": { - "message": "Bitwarden autofill menu button", + "message": "دکمه منوی پر کردن خودکار Bitwarden", "description": "Page title for the iframe containing the overlay button" }, "toggleBitwardenVaultOverlay": { - "message": "Toggle Bitwarden autofill menu", + "message": "نمایش یا مخفی کردن منوی پر کردن خودکار Bitwarden", "description": "Screen reader and tool tip label for the overlay button" }, "bitwardenVault": { - "message": "Bitwarden autofill menu", + "message": "منوی پر کردن خودکار Bitwarden", "description": "Page title in overlay" }, "unlockYourAccountToViewMatchingLogins": { - "message": "Unlock your account to view matching logins", + "message": "برای مشاهده ورودهای منطبق، قفل حساب کاربری خود را باز کنید", "description": "Text to display in overlay when the account is locked." }, "unlockYourAccountToViewAutofillSuggestions": { - "message": "Unlock your account to view autofill suggestions", + "message": "برای مشاهده پیشنهادهای پر کردن خودکار، قفل حساب کاربری خود را باز کنید", "description": "Text to display in overlay when the account is locked." }, "unlockAccount": { - "message": "Unlock account", + "message": "قفل حساب کاربری را باز کنید", "description": "Button text to display in overlay when the account is locked." }, "unlockAccountAria": { - "message": "Unlock your account, opens in a new window", + "message": "قفل حساب کاربری خود را باز کنید، در پنجره‌ای جدید باز می‌شود", "description": "Screen reader text (aria-label) for unlock account button in overlay" }, "totpCodeAria": { - "message": "Time-based One-Time Password Verification Code", + "message": "کد تأیید رمز یک‌بار مصرف مبتنی بر زمان", "description": "Aria label for the totp code displayed in the inline menu for autofill" }, "totpSecondsSpanAria": { - "message": "Time remaining before current TOTP expires", + "message": "زمان باقی‌مانده تا انقضای کد TOTP فعلی", "description": "Aria label for the totp seconds displayed in the inline menu for autofill" }, "fillCredentialsFor": { - "message": "Fill credentials for", + "message": "پر کردن اطلاعات ورود برای", "description": "Screen reader text for when overlay item is in focused" }, "partialUsername": { - "message": "Partial username", + "message": "نام کاربری جزئی", "description": "Screen reader text for when a login item is focused where a partial username is displayed. SR will announce this phrase before reading the text of the partial username" }, "noItemsToShow": { @@ -3734,31 +3816,31 @@ "description": "Screen reader text (aria-label) for new item button in overlay" }, "newLogin": { - "message": "New login", + "message": "ورود جدید", "description": "Button text to display within inline menu when there are no matching items on a login field" }, "addNewLoginItemAria": { - "message": "Add new vault login item, opens in a new window", + "message": "افزودن مورد ورود جدید به گاوصندوق، در پنجره‌ای جدید باز می‌شود", "description": "Screen reader text (aria-label) for new login button within inline menu" }, "newCard": { - "message": "New card", + "message": "کارت جدید", "description": "Button text to display within inline menu when there are no matching items on a credit card field" }, "addNewCardItemAria": { - "message": "Add new vault card item, opens in a new window", + "message": "افزودن کارت جدید به گاوصندوق، در پنجره‌ای جدید باز می‌شود", "description": "Screen reader text (aria-label) for new card button within inline menu" }, "newIdentity": { - "message": "New identity", + "message": "هویت جدید", "description": "Button text to display within inline menu when there are no matching items on an identity field" }, "addNewIdentityItemAria": { - "message": "Add new vault identity item, opens in a new window", + "message": "افزودن هویت جدید به گاوصندوق، در پنجره‌ای جدید باز می‌شود", "description": "Screen reader text (aria-label) for new identity button within inline menu" }, "bitwardenOverlayMenuAvailable": { - "message": "Bitwarden autofill menu available. Press the down arrow key to select.", + "message": "منوی پر کردن خودکار Bitwarden در دسترس است. برای انتخاب، کلید فلش پایین را فشار دهید.", "description": "Screen reader text for announcing when the overlay opens on the page" }, "turnOn": { @@ -3772,13 +3854,13 @@ "description": "Used for the header of the import dialog, the import button and within the file-password-prompt" }, "importError": { - "message": "خطای وارد کردن" + "message": "خطای درون ریزی" }, "importErrorDesc": { "message": "مشکلی با داده‌هایی که سعی کردید وارد کنید وجود داشت. لطفاً خطاهای فهرست شده زیر را در فایل منبع خود برطرف کرده و دوباره امتحان کنید." }, "resolveTheErrorsBelowAndTryAgain": { - "message": "Resolve the errors below and try again." + "message": "خطاهای زیر را برطرف کرده و دوباره امتحان کنید." }, "description": { "message": "توضیحات" @@ -3799,10 +3881,10 @@ "message": "دوباره سعی کنید" }, "verificationRequiredForActionSetPinToContinue": { - "message": "Verification required for this action. Set a PIN to continue." + "message": "برای این اقدام تأیید لازم است. یک کد پین تعیین کنید تا ادامه دهید." }, "setPin": { - "message": "تنظیم PIN" + "message": "تنظیم کد پین" }, "verifyWithBiometrics": { "message": "تایید با استفاده از بیومتریک" @@ -3817,25 +3899,25 @@ "message": "نیازمند روش دیگری هستید؟" }, "useMasterPassword": { - "message": "استفاده از رمز عبور اصلی" + "message": "استفاده از کلمه عبور اصلی" }, "usePin": { - "message": "استفاده از PIN" + "message": "استفاده از کد پین" }, "useBiometrics": { "message": "استفاده از بیومتریک" }, "enterVerificationCodeSentToEmail": { - "message": "Enter the verification code that was sent to your email." + "message": "کد تأییدی را که به ایمیل شما ارسال شده است وارد کنید." }, "resendCode": { - "message": "Resend code" + "message": "ارسال دوباره کد" }, "total": { "message": "مجموع" }, "importWarning": { - "message": "You are importing data to $ORGANIZATION$. Your data may be shared with members of this organization. Do you want to proceed?", + "message": "شما در حال درون ریزی داده به $ORGANIZATION$ هستید. داده‌های شما ممکن است با اعضای این سازمان به اشتراک گذاشته شود. آیا می‌خواهید ادامه دهید؟", "placeholders": { "organization": { "content": "$1", @@ -3844,13 +3926,13 @@ } }, "duoHealthCheckResultsInNullAuthUrlError": { - "message": "Error connecting with the Duo service. Use a different two-step login method or contact Duo for assistance." + "message": "خطا در اتصال به سرویس Duo. از روش ورود دو مرحله‌ای دیگری استفاده کنید یا برای دریافت کمک با Duo تماس بگیرید." }, "duoRequiredForAccount": { - "message": "Duo two-step login is required for your account." + "message": "ورود دو مرحله ای Duo برای حساب کاربری شما لازم است." }, "popoutExtension": { - "message": "Popout extension" + "message": "باز کردن پنجره جداگانه افزونه" }, "launchDuo": { "message": "اجرای Duo" @@ -3862,25 +3944,25 @@ "message": "چیزی وارد نشد." }, "importEncKeyError": { - "message": "Error decrypting the exported file. Your encryption key does not match the encryption key used export the data." + "message": "خطا در رمزگشایی پرونده‌ی درون ریزی شده. کلید رمزگذاری شما با کلید رمزگذاری استفاده شده برای درون ریزی داده‌ها مطابقت ندارد." }, "invalidFilePassword": { - "message": "Invalid file password, please use the password you entered when you created the export file." + "message": "کلمه عبور پرونده نامعتبر است، لطفاً از کلمه عبوری که هنگام ایجاد پرونده‌ی برون ریزی وارد کردید استفاده کنید." }, "destination": { - "message": "Destination" + "message": "مقصد" }, "learnAboutImportOptions": { - "message": "Learn about your import options" + "message": "درباره گزینه‌های برون ریزی خود بیاموزید" }, "selectImportFolder": { "message": "یک پوشه انتخاب کنید" }, "selectImportCollection": { - "message": "Select a collection" + "message": "انتخاب یک مجموعه" }, "importTargetHint": { - "message": "Select this option if you want the imported file contents moved to a $DESTINATION$", + "message": "اگر می‌خواهید محتوای فایل وارد شده به $DESTINATION$ منتقل شود، این گزینه را انتخاب کنید", "description": "Located as a hint under the import target. Will be appended by either folder or collection, depending if the user is importing into an individual or an organizational vault.", "placeholders": { "destination": { @@ -3890,25 +3972,25 @@ } }, "importUnassignedItemsError": { - "message": "File contains unassigned items." + "message": "پرونده حاوی موارد اختصاص نیافته است." }, "selectFormat": { - "message": "Select the format of the import file" + "message": "فرمت پرونده‌ی درون ریزی را انتخاب کنید" }, "selectImportFile": { - "message": "Select the import file" + "message": "پرونده‌ی درون ریزی را انتخاب کنید" }, "chooseFile": { - "message": "Choose File" + "message": "انتخاب پرونده" }, "noFileChosen": { - "message": "No file chosen" + "message": "هیچ پرونده‌ای انتخاب نشد" }, "orCopyPasteFileContents": { - "message": "or copy/paste the import file contents" + "message": "یا محتویات پرونده‌ی درون ریزی را کپی/پیست کنید" }, "instructionsFor": { - "message": "$NAME$ Instructions", + "message": "دستورالعمل‌های $NAME$", "description": "The title for the import tool instructions.", "placeholders": { "name": { @@ -3918,25 +4000,25 @@ } }, "confirmVaultImport": { - "message": "Confirm vault import" + "message": "درون ریزی گاوصندوق را تأیید کنید" }, "confirmVaultImportDesc": { - "message": "This file is password-protected. Please enter the file password to import data." + "message": "این پرونده با کلمه عبور محافظت شده است. لطفاً کلمه عبور پرونده را برای درون ریزی داده‌ها وارد کنید." }, "confirmFilePassword": { - "message": "Confirm file password" + "message": "تأیید کلمه عبور پرونده" }, "exportSuccess": { - "message": "Vault data exported" + "message": "داده های گاوصندوق برون ریزی شد" }, "typePasskey": { - "message": "Passkey" + "message": "کلید عبور" }, "accessing": { - "message": "Accessing" + "message": "در حال دسترسی" }, "loggedInExclamation": { - "message": "Logged in!" + "message": "وارد شده!" }, "passkeyNotCopied": { "message": "کلید عبور کپی نمی‌شود" @@ -3948,7 +4030,7 @@ "message": "تأیید توسط سایت آغازگر الزامی است. این ویژگی هنوز برای حساب‌های بدون کلمه عبور اصلی اجرا نشده است." }, "logInWithPasskeyQuestion": { - "message": "Log in with passkey?" + "message": "با کلید عبور وارد می‌شوید؟" }, "passkeyAlreadyExists": { "message": "یک کلید عبور از قبل برای این برنامه وجود دارد." @@ -3960,10 +4042,10 @@ "message": "شما هیچ ورود مشابهی برای این سایت ندارید." }, "noMatchingLoginsForSite": { - "message": "No matching logins for this site" + "message": "ورود منطبق برای این سایت یافت نشد" }, "searchSavePasskeyNewLogin": { - "message": "Search or save passkey as new login" + "message": "جستجو یا ذخیره کلید عبور به عنوان ورود جدید" }, "confirm": { "message": "تأیید" @@ -3975,10 +4057,10 @@ "message": "کلید عبور را به عنوان ورود جدید ذخیره کن" }, "chooseCipherForPasskeySave": { - "message": "Choose a login to save this passkey to" + "message": "یک ورود برای ذخیره این کلید عبور انتخاب کنید" }, "chooseCipherForPasskeyAuth": { - "message": "Choose a passkey to log in with" + "message": "یک کلید عبور برای ورود انتخاب کنید" }, "passkeyItem": { "message": "مورد کلید عبور" @@ -3996,125 +4078,125 @@ "message": "برای استفاده از کلید عبور، احراز هویت لازم است. برای ادامه، هویت خود را تأیید کنید." }, "multifactorAuthenticationCancelled": { - "message": "Multifactor authentication cancelled" + "message": "تأیید هویت چند مرحله‌ای کنسل شد" }, "noLastPassDataFound": { - "message": "No LastPass data found" + "message": "هیچ داده‌ای از LastPass یافت نشد" }, "incorrectUsernameOrPassword": { - "message": "Incorrect username or password" + "message": "نام کاربری یا کلمه عبور اشتباه است" }, "incorrectPassword": { - "message": "Incorrect password" + "message": "کلمه عبور اشتباه است" }, "incorrectCode": { - "message": "Incorrect code" + "message": "کد اشتباه است" }, "incorrectPin": { - "message": "Incorrect PIN" + "message": "کد پین نادرست است" }, "multifactorAuthenticationFailed": { - "message": "Multifactor authentication failed" + "message": "احراز هویت چند مرحله‌ای ناموفق بود" }, "includeSharedFolders": { - "message": "Include shared folders" + "message": "شامل پوشه‌های به‌ اشتراک گذاری‌ شده" }, "lastPassEmail": { - "message": "LastPass Email" + "message": "ایمیل LastPass" }, "importingYourAccount": { - "message": "Importing your account..." + "message": "در حال درون ریزی حساب کاربری شما..." }, "lastPassMFARequired": { - "message": "LastPass multifactor authentication required" + "message": "احراز هویت چند مرحله‌ای LastPass مورد نیاز است" }, "lastPassMFADesc": { - "message": "Enter your one-time passcode from your authentication app" + "message": "کد یک‌بار مصرف خود را از برنامه احراز هویت وارد کنید" }, "lastPassOOBDesc": { - "message": "Approve the login request in your authentication app or enter a one-time passcode." + "message": "درخواست ورود را در برنامه احراز هویت خود تأیید کنید یا یک کد یک‌بار مصرف وارد کنید." }, "passcode": { - "message": "Passcode" + "message": "کد عبور" }, "lastPassMasterPassword": { - "message": "LastPass master password" + "message": "کلمه عبور اصلی LastPass" }, "lastPassAuthRequired": { - "message": "LastPass authentication required" + "message": "احراز هویت LastPass مورد نیاز است" }, "awaitingSSO": { - "message": "Awaiting SSO authentication" + "message": "در انتظار احراز هویت SSO" }, "awaitingSSODesc": { - "message": "Please continue to log in using your company credentials." + "message": "لطفاً برای ورود، از اطلاعات کاربری شرکت خود استفاده کنید." }, "seeDetailedInstructions": { - "message": "See detailed instructions on our help site at", + "message": "دستورالعمل‌های کامل را در سایت راهنمای ما مشاهده کنید در", "description": "This is followed a by a hyperlink to the help website." }, "importDirectlyFromLastPass": { - "message": "Import directly from LastPass" + "message": "درون ریزی مستقیم از LastPass" }, "importFromCSV": { - "message": "Import from CSV" + "message": "درون ریزی از CSV" }, "lastPassTryAgainCheckEmail": { - "message": "Try again or look for an email from LastPass to verify it's you." + "message": "دوباره تلاش کنید یا ایمیلی از LastPass را بررسی کنید تا هویت شما تأیید شود." }, "collection": { - "message": "Collection" + "message": "مجموعه" }, "lastPassYubikeyDesc": { - "message": "Insert the YubiKey associated with your LastPass account into your computer's USB port, then touch its button." + "message": "کلید YubiKey مربوط به حساب کاربری LastPass خود را در درگاه USB کامپیوتر قرار دهید، سپس دکمه آن را لمس کنید." }, "switchAccount": { - "message": "Switch account" + "message": "تعویض حساب کاربری" }, "switchAccounts": { - "message": "Switch accounts" + "message": "تعویض حساب‌ها" }, "switchToAccount": { - "message": "Switch to account" + "message": "تعویض به حساب کاربری" }, "activeAccount": { - "message": "Active account" + "message": "حساب کاربری فعال" }, "bitwardenAccount": { - "message": "Bitwarden account" + "message": "حساب کاربری Bitwarden" }, "availableAccounts": { - "message": "Available accounts" + "message": "حساب کاربری در درسترس" }, "accountLimitReached": { - "message": "Account limit reached. Log out of an account to add another." + "message": "محدودیت حساب کاربری تکمیل شد. برای افزودن حساب کاربری دیگر، از یک حساب خارج شوید." }, "active": { - "message": "active" + "message": "فعال" }, "locked": { - "message": "locked" + "message": "قفل شد" }, "unlocked": { - "message": "unlocked" + "message": "باز شد" }, "server": { - "message": "server" + "message": "سرور" }, "hostedAt": { - "message": "hosted at" + "message": "میزبانی شده در" }, "useDeviceOrHardwareKey": { - "message": "Use your device or hardware key" + "message": "از دستگاه یا کلید سخت‌افزاری خود استفاده کنید" }, "justOnce": { - "message": "Just once" + "message": "فقط یک بار" }, "alwaysForThisSite": { - "message": "Always for this site" + "message": "همیشه برای این سایت" }, "domainAddedToExcludedDomains": { - "message": "$DOMAIN$ added to excluded domains.", + "message": "دامنه $DOMAIN$ به دامنه‌های مستثنی اضافه شد.", "placeholders": { "domain": { "content": "$1", @@ -4123,106 +4205,106 @@ } }, "commonImportFormats": { - "message": "Common formats", + "message": "فرمت‌های رایج", "description": "Label indicating the most common import formats" }, "confirmContinueToBrowserSettingsTitle": { - "message": "Continue to browser settings?", + "message": "ادامه به تنظیمات مرورگر؟", "description": "Title for dialog which asks if the user wants to proceed to a relevant browser settings page" }, "confirmContinueToHelpCenter": { - "message": "Continue to Help Center?", + "message": "به مرکز راهنمایی ادامه می‌دهید؟", "description": "Title for dialog which asks if the user wants to proceed to a relevant Help Center page" }, "confirmContinueToHelpCenterPasswordManagementContent": { - "message": "Change your browser's autofill and password management settings.", + "message": "تنظیمات پر کردن خودکار و مدیریت کلمه عبور مرورگر خود را تغییر دهید.", "description": "Body content for dialog which asks if the user wants to proceed to the Help Center's page about browser password management settings" }, "confirmContinueToHelpCenterKeyboardShortcutsContent": { - "message": "You can view and set extension shortcuts in your browser's settings.", + "message": "می‌توانید میان‌برهای افزونه را در تنظیمات مرورگر خود مشاهده و تنظیم کنید.", "description": "Body content for dialog which asks if the user wants to proceed to the Help Center's page about browser keyboard shortcut settings" }, "confirmContinueToBrowserPasswordManagementSettingsContent": { - "message": "Change your browser's autofill and password management settings.", + "message": "تنظیمات پر کردن خودکار و مدیریت کلمه عبور مرورگر خود را تغییر دهید.", "description": "Body content for dialog which asks if the user wants to proceed to the browser's password management settings page" }, "confirmContinueToBrowserKeyboardShortcutSettingsContent": { - "message": "You can view and set extension shortcuts in your browser's settings.", + "message": "می‌توانید میان‌برهای افزونه را در تنظیمات مرورگر خود مشاهده و تنظیم کنید.", "description": "Body content for dialog which asks if the user wants to proceed to the browser's keyboard shortcut settings page" }, "overrideDefaultBrowserAutofillTitle": { - "message": "Make Bitwarden your default password manager?", + "message": "می‌خواهید Bitwarden را مدیر کلمه عبور پیش‌فرض خود کنید؟", "description": "Dialog title facilitating the ability to override a chrome browser's default autofill behavior" }, "overrideDefaultBrowserAutofillDescription": { - "message": "Ignoring this option may cause conflicts between Bitwarden autofill suggestions and your browser's.", + "message": "نادیده گرفتن این گزینه ممکن است باعث تداخل بین پیشنهادهای پر کردن خودکار Bitwarden و مرورگر شما شود.", "description": "Dialog message facilitating the ability to override a chrome browser's default autofill behavior" }, "overrideDefaultBrowserAutoFillSettings": { - "message": "Make Bitwarden your default password manager", + "message": "Bitwarden را به عنوان مدیر کلمه عبور پیش‌فرض خود تنظیم کنید", "description": "Label for the setting that allows overriding the default browser autofill settings" }, "privacyPermissionAdditionNotGrantedTitle": { - "message": "Unable to set Bitwarden as the default password manager", + "message": "امکان تنظیم Bitwarden به‌عنوان مدیر کلمه عبور پیش‌فرض وجود ندارد", "description": "Title for the dialog that appears when the user has not granted the extension permission to set privacy settings" }, "privacyPermissionAdditionNotGrantedDescription": { - "message": "You must grant browser privacy permissions to Bitwarden to set it as the default password manager.", + "message": "برای تنظیم Bitwarden به‌عنوان مدیر کلمه عبور پیش‌فرض، باید دسترسی‌های حفظ حریم خصوصی مرورگر را به آن بدهید.", "description": "Description for the dialog that appears when the user has not granted the extension permission to set privacy settings" }, "makeDefault": { - "message": "Make default", + "message": "تنظیم به‌عنوان پیش‌فرض", "description": "Button text for the setting that allows overriding the default browser autofill settings" }, "saveCipherAttemptSuccess": { - "message": "Credentials saved successfully!", + "message": "اطلاعات ورود با موفقیت ذخیره شد!", "description": "Notification message for when saving credentials has succeeded." }, "passwordSaved": { - "message": "Password saved!", + "message": "کلمه عبور ذخیره شد!", "description": "Notification message for when saving credentials has succeeded." }, "updateCipherAttemptSuccess": { - "message": "Credentials updated successfully!", + "message": "اطلاعات ورود با موفقیت به‌روزرسانی شد!", "description": "Notification message for when updating credentials has succeeded." }, "passwordUpdated": { - "message": "Password updated!", + "message": "کلمه عبور به‌روزرسانی شد!", "description": "Notification message for when updating credentials has succeeded." }, "saveCipherAttemptFailed": { - "message": "Error saving credentials. Check console for details.", + "message": "خطا در ذخیره اطلاعات ورود. جزئیات را در کنسول بررسی کنید.", "description": "Notification message for when saving credentials has failed." }, "success": { - "message": "Success" + "message": "موفقیت آمیز بود" }, "removePasskey": { - "message": "Remove passkey" + "message": "حذف کلید عبور" }, "passkeyRemoved": { - "message": "Passkey removed" + "message": "کلید عبور حذف شد" }, "autofillSuggestions": { - "message": "Autofill suggestions" + "message": "پیشنهادهای پر کردن خودکار" }, "itemSuggestions": { - "message": "Suggested items" + "message": "موارد پیشنهادی" }, "autofillSuggestionsTip": { - "message": "Save a login item for this site to autofill" + "message": "یک مورد ورود برای این سایت ذخیره کنید تا به‌صورت خودکار پر شود" }, "yourVaultIsEmpty": { - "message": "Your vault is empty" + "message": "گاوصندوق‌تان خالی است" }, "noItemsMatchSearch": { - "message": "No items match your search" + "message": "موردی با جستجوی شما مطابقت نداشت" }, "clearFiltersOrTryAnother": { - "message": "Clear filters or try another search term" + "message": "فیلترها را پاک کنید یا عبارت جستجوی دیگری را امتحان کنید" }, "copyInfoTitle": { - "message": "Copy info - $ITEMNAME$", + "message": "کپی اطلاعات - $ITEMNAME$", "description": "Title for a button that opens a menu with options to copy information from an item.", "placeholders": { "itemname": { @@ -4232,7 +4314,7 @@ } }, "copyNoteTitle": { - "message": "Copy Note - $ITEMNAME$", + "message": "کپی یادداشت - $ITEMNAME$", "description": "Title for a button copies a note to the clipboard.", "placeholders": { "itemname": { @@ -4242,7 +4324,7 @@ } }, "moreOptionsLabel": { - "message": "More options, $ITEMNAME$", + "message": "گزینه‌های بیشتر، $ITEMNAME$", "description": "Aria label for a button that opens a menu with more options for an item.", "placeholders": { "itemname": { @@ -4252,7 +4334,7 @@ } }, "moreOptionsTitle": { - "message": "More options - $ITEMNAME$", + "message": "گزینه‌های بیشتر - $ITEMNAME$", "description": "Title for a button that opens a menu with more options for an item.", "placeholders": { "itemname": { @@ -4262,7 +4344,7 @@ } }, "viewItemTitle": { - "message": "View item - $ITEMNAME$", + "message": "مشاهده آیتم - $ITEMNAME$", "description": "Title for a link that opens a view for an item.", "placeholders": { "itemname": { @@ -4272,7 +4354,7 @@ } }, "viewItemTitleWithField": { - "message": "View item - $ITEMNAME$ - $FIELD$", + "message": "مشاهده مورد - $ITEMNAME$ - $FIELD$", "description": "Title for a link that opens a view for an item.", "placeholders": { "itemname": { @@ -4286,7 +4368,7 @@ } }, "autofillTitle": { - "message": "Autofill - $ITEMNAME$", + "message": "پر کردن خودکار - $ITEMNAME$", "description": "Title for a button that autofills a login item.", "placeholders": { "itemname": { @@ -4296,7 +4378,7 @@ } }, "autofillTitleWithField": { - "message": "Autofill - $ITEMNAME$ - $FIELD$", + "message": "پر کردن خودکار - $ITEMNAME$ - $FIELD$", "description": "Title for a button that autofills a login item.", "placeholders": { "itemname": { @@ -4310,7 +4392,7 @@ } }, "copyFieldValue": { - "message": "Copy $FIELD$, $VALUE$", + "message": "کپی $FIELD$، $VALUE$", "description": "Title for a button that copies a field value to the clipboard.", "placeholders": { "field": { @@ -4324,40 +4406,40 @@ } }, "noValuesToCopy": { - "message": "No values to copy" + "message": "هیچ مقداری برای کپی وجود ندارد" }, "assignToCollections": { - "message": "Assign to collections" + "message": "اختصاص به مجموعه‌ها" }, "copyEmail": { - "message": "Copy email" + "message": "کپی ایمیل" }, "copyPhone": { - "message": "Copy phone" + "message": "کپی تلفن" }, "copyAddress": { - "message": "Copy address" + "message": "کپی آدرس" }, "adminConsole": { - "message": "Admin Console" + "message": "کنسول مدیر" }, "accountSecurity": { - "message": "Account security" + "message": "امنیت حساب کاربری" }, "notifications": { - "message": "Notifications" + "message": "اعلان‌ها" }, "appearance": { - "message": "Appearance" + "message": "ظاهر" }, "errorAssigningTargetCollection": { - "message": "Error assigning target collection." + "message": "خطا در اختصاص مجموعه هدف." }, "errorAssigningTargetFolder": { - "message": "Error assigning target folder." + "message": "خطا در اختصاص پوشه هدف." }, "viewItemsIn": { - "message": "View items in $NAME$", + "message": "مشاهده موارد در $NAME$", "description": "Button to view the contents of a folder or collection", "placeholders": { "name": { @@ -4367,7 +4449,7 @@ } }, "backTo": { - "message": "Back to $NAME$", + "message": "بازگشت به $NAME$", "description": "Navigate back to a previous folder or collection", "placeholders": { "name": { @@ -4377,10 +4459,10 @@ } }, "new": { - "message": "New" + "message": "جدید" }, "removeItem": { - "message": "Remove $NAME$", + "message": "حذف $NAME$", "description": "Remove a selected option, such as a folder or collection", "placeholders": { "name": { @@ -4390,56 +4472,56 @@ } }, "itemsWithNoFolder": { - "message": "Items with no folder" + "message": "موارد بدون پوشه" }, "itemDetails": { - "message": "Item details" + "message": "جزئیات مورد" }, "itemName": { - "message": "Item name" + "message": "نام مورد" }, "organizationIsDeactivated": { - "message": "Organization is deactivated" + "message": "سازمان غیرفعال شده است" }, "owner": { - "message": "Owner" + "message": "مالک" }, "selfOwnershipLabel": { - "message": "You", + "message": "شما", "description": "Used as a label to indicate that the user is the owner of an item." }, "contactYourOrgAdmin": { - "message": "Items in deactivated organizations cannot be accessed. Contact your organization owner for assistance." + "message": "به موردهای موجود در سازمان‌های غیرفعال نمی‌توان دسترسی داشت. برای دریافت کمک با مالک سازمان تماس بگیرید." }, "additionalInformation": { - "message": "Additional information" + "message": "اطلاعات بیشتر" }, "itemHistory": { - "message": "Item history" + "message": "تاریخچه مورد" }, "lastEdited": { - "message": "Last edited" + "message": "آخرین ویرایش" }, "ownerYou": { - "message": "Owner: You" + "message": "مالک: شما" }, "linked": { - "message": "Linked" + "message": "پیوند شده" }, "copySuccessful": { - "message": "Copy Successful" + "message": "کپی موفق بود" }, "upload": { - "message": "Upload" + "message": "بارگذاری" }, "addAttachment": { - "message": "Add attachment" + "message": "افزودن پیوست" }, "maxFileSizeSansPunctuation": { - "message": "Maximum file size is 500 MB" + "message": "بیشترین حجم پرونده ۵۰۰ مگابایت است" }, "deleteAttachmentName": { - "message": "Delete attachment $NAME$", + "message": "حذف پیوست $NAME$", "placeholders": { "name": { "content": "$1", @@ -4448,7 +4530,7 @@ } }, "downloadAttachmentName": { - "message": "Download $NAME$", + "message": "بارگیری $NAME$", "placeholders": { "name": { "content": "$1", @@ -4456,26 +4538,53 @@ } } }, + "downloadBitwarden": { + "message": "بارگیری Bitwarden" + }, + "downloadBitwardenOnAllDevices": { + "message": "Bitwarden را روی همه دستگاه‌ها بارگیری کنید" + }, + "getTheMobileApp": { + "message": "دریافت برنامه‌ی تلفن همراه" + }, + "getTheMobileAppDesc": { + "message": "با برنامه موبایل Bitwarden، به کلمات عبور خود در هر زمان و مکان دسترسی داشته باشید." + }, + "getTheDesktopApp": { + "message": "برنامه دسکتاپ را دریافت کنید" + }, + "getTheDesktopAppDesc": { + "message": "بدون استفاده از مرورگر به گاوصندوق خود دسترسی پیدا کنید و سپس باز کردن قفل با بیومتریک را تنظیم کنید تا باز کردن سریع‌تر قفل در اپ دسکتاپ و افزونه مرورگر انجام شود." + }, + "downloadFromBitwardenNow": { + "message": "هم‌اکنون از bitwarden.com بارگیری کنید" + }, + "getItOnGooglePlay": { + "message": "از Google Play دریافت کنید" + }, + "downloadOnTheAppStore": { + "message": "از AppStore بارگیری کنید" + }, "permanentlyDeleteAttachmentConfirmation": { - "message": "Are you sure you want to permanently delete this attachment?" + "message": "آیا مطمئن هستید که می‌خواهید این پرونده پیوست را به‌طور دائمی حذف کنید؟" }, "premium": { - "message": "Premium" + "message": "پرمیوم" }, "freeOrgsCannotUseAttachments": { - "message": "Free organizations cannot use attachments" + "message": "سازمان‌های رایگان نمی‌توانند از پرونده‌های پیوست استفاده کنند" }, "filters": { - "message": "Filters" + "message": "فیلترها" }, "filterVault": { - "message": "Filter vault" + "message": "فیلتر گاوصندوق" }, "filterApplied": { - "message": "One filter applied" + "message": "یک فیلتر اعمال شده است" }, "filterAppliedPlural": { - "message": "$COUNT$ filters applied", + "message": "$COUNT$ فیلتر اعمال شده است", "placeholders": { "count": { "content": "$1", @@ -4484,16 +4593,16 @@ } }, "personalDetails": { - "message": "Personal details" + "message": "جزئیات شخصی" }, "identification": { - "message": "Identification" + "message": "شناسایی" }, "contactInfo": { - "message": "Contact info" + "message": "اطلاعات مخاطب" }, "downloadAttachment": { - "message": "Download - $ITEMNAME$", + "message": "بارگیری - $ITEMNAME$", "placeholders": { "itemname": { "content": "$1", @@ -4502,23 +4611,23 @@ } }, "cardNumberEndsWith": { - "message": "card number ends with", + "message": "شماره کارت پایان می‌یابد با", "description": "Used within the inline menu to provide an aria description when users are attempting to fill a card cipher." }, "loginCredentials": { - "message": "Login credentials" + "message": "اطلاعات ورود" }, "authenticatorKey": { - "message": "Authenticator key" + "message": "کلید احراز هویت" }, "autofillOptions": { - "message": "Autofill options" + "message": "گزینه‌های پر کردن خودکار" }, "websiteUri": { - "message": "Website (URI)" + "message": "وب‌سایت (نشانی اینترنتی)" }, "websiteUriCount": { - "message": "Website (URI) $COUNT$", + "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": { @@ -4528,16 +4637,16 @@ } }, "websiteAdded": { - "message": "Website added" + "message": "وب‌سایت اضافه شد" }, "addWebsite": { - "message": "Add website" + "message": "افزودن وب‌سایت" }, "deleteWebsite": { - "message": "Delete website" + "message": "حذف وبسایت" }, "defaultLabel": { - "message": "Default ($VALUE$)", + "message": "پیش‌فرض ($VALUE$)", "description": "A label that indicates the default value for a field with the current default value in parentheses.", "placeholders": { "value": { @@ -4547,7 +4656,7 @@ } }, "showMatchDetection": { - "message": "Show match detection $WEBSITE$", + "message": "نمایش تطبیق سایت $WEBSITE$", "placeholders": { "website": { "content": "$1", @@ -4556,7 +4665,7 @@ } }, "hideMatchDetection": { - "message": "Hide match detection $WEBSITE$", + "message": "مخفی کردن تطبیق سایت $WEBSITE$", "placeholders": { "website": { "content": "$1", @@ -4565,19 +4674,19 @@ } }, "autoFillOnPageLoad": { - "message": "Autofill on page load?" + "message": "پر کردن خودکار هنگام بارگذاری صفحه؟" }, "cardExpiredTitle": { - "message": "Expired card" + "message": "تاریخ کارت منقضی شده است" }, "cardExpiredMessage": { - "message": "If you've renewed it, update the card's information" + "message": "اگر تمدید کرده‌اید، اطلاعات کارت را به‌روزرسانی کنید" }, "cardDetails": { - "message": "Card details" + "message": "جزئیات کارت" }, "cardBrandDetails": { - "message": "$BRAND$ details", + "message": "$BRAND$ جزئیات", "placeholders": { "brand": { "content": "$1", @@ -4586,43 +4695,43 @@ } }, "enableAnimations": { - "message": "Enable animations" + "message": "فعال‌سازی انیمیشن‌ها" }, "showAnimations": { - "message": "Show animations" + "message": "نمایش انیمیشن‌ها" }, "addAccount": { - "message": "Add account" + "message": "افزودن حساب کاربری" }, "loading": { - "message": "Loading" + "message": "در حال بارگذاری" }, "data": { - "message": "Data" + "message": "داده‌" }, "passkeys": { - "message": "Passkeys", + "message": "کلیدهای عبور", "description": "A section header for a list of passkeys." }, "passwords": { - "message": "Passwords", + "message": "کلمات عبور", "description": "A section header for a list of passwords." }, "logInWithPasskeyAriaLabel": { - "message": "Log in with passkey", + "message": "با کلید عبور وارد شوید", "description": "ARIA label for the inline menu button that logs in with a passkey." }, "assign": { - "message": "Assign" + "message": "اختصاص بدهید" }, "bulkCollectionAssignmentDialogDescriptionSingular": { - "message": "Only organization members with access to these collections will be able to see the item." + "message": "فقط اعضای سازمانی که به این مجموعه‌ها دسترسی دارند قادر به مشاهده این مورد خواهند بود." }, "bulkCollectionAssignmentDialogDescriptionPlural": { - "message": "Only organization members with access to these collections will be able to see the items." + "message": "فقط اعضای سازمانی که به این مجموعه‌ها دسترسی دارند قادر به مشاهده این موارد خواهند بود." }, "bulkCollectionAssignmentWarning": { - "message": "You have selected $TOTAL_COUNT$ items. You cannot update $READONLY_COUNT$ of the items because you do not have edit permissions.", + "message": "شما $TOTAL_COUNT$ مورد را انتخاب کرده‌اید. نمی‌توانید $READONLY_COUNT$ مورد را به‌روزرسانی کنید زیرا دسترسی ویرایش ندارید.", "placeholders": { "total_count": { "content": "$1", @@ -4634,37 +4743,37 @@ } }, "addField": { - "message": "Add field" + "message": "افزودن فیلد" }, "add": { - "message": "Add" + "message": "افزودن" }, "fieldType": { - "message": "Field type" + "message": "نوع فیلد" }, "fieldLabel": { - "message": "Field label" + "message": "برچسب فیلد" }, "textHelpText": { - "message": "Use text fields for data like security questions" + "message": "برای داده‌هایی مانند سوالات امنیتی از فیلدهای متنی استفاده کنید" }, "hiddenHelpText": { - "message": "Use hidden fields for sensitive data like a password" + "message": "برای داده‌های حساس مانند کلمه عبور از فیلدهای مخفی استفاده کنید" }, "checkBoxHelpText": { - "message": "Use checkboxes if you'd like to autofill a form's checkbox, like a remember email" + "message": "اگر می‌خواهید فیلدهای تیک‌دار فرم را به‌صورت خودکار پر کنید، مانند گزینه به یاد سپردن ایمیل، از کادرهای انتخاب استفاده کنید" }, "linkedHelpText": { - "message": "Use a linked field when you are experiencing autofill issues for a specific website." + "message": "وقتی در پر کردن خودکار برای یک وب‌سایت خاص به مشکل برخوردید، از فیلد مرتبط استفاده کنید." }, "linkedLabelHelpText": { - "message": "Enter the the field's html id, name, aria-label, or placeholder." + "message": "شناسه Html، نام، aria-label یا محل نگهدار فیلد را وارد کنید." }, "editField": { - "message": "Edit field" + "message": "ویرایش فیلد" }, "editFieldLabel": { - "message": "Edit $LABEL$", + "message": "ویرایش $LABEL$", "placeholders": { "label": { "content": "$1", @@ -4673,7 +4782,7 @@ } }, "deleteCustomField": { - "message": "Delete $LABEL$", + "message": "حذف $LABEL$", "placeholders": { "label": { "content": "$1", @@ -4682,7 +4791,7 @@ } }, "fieldAdded": { - "message": "$LABEL$ added", + "message": "$LABEL$ افزوده شد", "placeholders": { "label": { "content": "$1", @@ -4691,7 +4800,7 @@ } }, "reorderToggleButton": { - "message": "Reorder $LABEL$. Use arrow key to move item up or down.", + "message": "مرتب‌سازی مجدد $LABEL$. برای جابجایی مورد به بالا یا پایین از کلیدهای جهت‌نما استفاده کنید.", "placeholders": { "label": { "content": "$1", @@ -4700,10 +4809,10 @@ } }, "reorderWebsiteUriButton": { - "message": "Reorder website URI. Use arrow key to move item up or down." + "message": "مرتب‌سازی مجدد نشانی اینترنتی وب‌سایت. برای جابجایی مورد به بالا یا پایین از کلیدهای جهت‌نما استفاده کنید." }, "reorderFieldUp": { - "message": "$LABEL$ moved up, position $INDEX$ of $LENGTH$", + "message": "$LABEL$ به بالا منتقل شد، موقعیت $INDEX$ از $LENGTH$", "placeholders": { "label": { "content": "$1", @@ -4720,13 +4829,13 @@ } }, "selectCollectionsToAssign": { - "message": "Select collections to assign" + "message": "مجموعه‌ها را برای اختصاص انتخاب کنید" }, "personalItemTransferWarningSingular": { - "message": "1 item will be permanently transferred to the selected organization. You will no longer own this item." + "message": "۱ مورد به طور دائمی به سازمان انتخاب شده منتقل خواهد شد. شما دیگر مالک این مورد نخواهید بود." }, "personalItemsTransferWarningPlural": { - "message": "$PERSONAL_ITEMS_COUNT$ items will be permanently transferred to the selected organization. You will no longer own these items.", + "message": "$PERSONAL_ITEMS_COUNT$ مورد به طور دائمی به سازمان انتخاب شده منتقل خواهند شد. شما دیگر مالک این موارد نخواهید بود.", "placeholders": { "personal_items_count": { "content": "$1", @@ -4735,7 +4844,7 @@ } }, "personalItemWithOrgTransferWarningSingular": { - "message": "1 item will be permanently transferred to $ORG$. You will no longer own this item.", + "message": "۱ مورد به طور دائمی به $ORG$ منتقل خواهد شد. شما دیگر مالک این مورد نخواهید بود.", "placeholders": { "org": { "content": "$1", @@ -4744,7 +4853,7 @@ } }, "personalItemsWithOrgTransferWarningPlural": { - "message": "$PERSONAL_ITEMS_COUNT$ items will be permanently transferred to $ORG$. You will no longer own these items.", + "message": "$PERSONAL_ITEMS_COUNT$ مورد به طور دائمی به $ORG$ منتقل خواهند شد. شما دیگر مالک این موارد نخواهید بود.", "placeholders": { "personal_items_count": { "content": "$1", @@ -4757,13 +4866,13 @@ } }, "successfullyAssignedCollections": { - "message": "Successfully assigned collections" + "message": "مجموعه‌ها با موفقیت اختصاص داده شدند" }, "nothingSelected": { - "message": "You have not selected anything." + "message": "شما چیزی را انتخاب نکرده اید." }, "itemsMovedToOrg": { - "message": "Items moved to $ORGNAME$", + "message": "موارد به $ORGNAME$ منتقل شدند", "placeholders": { "orgname": { "content": "$1", @@ -4772,7 +4881,7 @@ } }, "itemMovedToOrg": { - "message": "Item moved to $ORGNAME$", + "message": "مورد به $ORGNAME$ منتقل شد", "placeholders": { "orgname": { "content": "$1", @@ -4781,7 +4890,7 @@ } }, "reorderFieldDown": { - "message": "$LABEL$ moved down, position $INDEX$ of $LENGTH$", + "message": "$LABEL$ به پایین منتقل شد، موقعیت $INDEX$ از $LENGTH$", "placeholders": { "label": { "content": "$1", @@ -4798,46 +4907,46 @@ } }, "itemLocation": { - "message": "Item Location" + "message": "مکان مورد" }, "fileSend": { - "message": "File Send" + "message": "پرونده ارسال" }, "fileSends": { - "message": "File Sends" + "message": "پرونده ارسال‌ها" }, "textSend": { - "message": "Text Send" + "message": "ارسال متن" }, "textSends": { - "message": "Text Sends" + "message": "ارسال‌های متن" }, "accountActions": { - "message": "Account actions" + "message": "فعالیت‌های حساب کاربری" }, "showNumberOfAutofillSuggestions": { - "message": "Show number of login autofill suggestions on extension icon" + "message": "نمایش تعداد پیشنهادهای پر کردن خودکار ورود در آیکون افزونه" }, "showQuickCopyActions": { - "message": "Show quick copy actions on Vault" + "message": "نمایش عملیات کپی سریع در گاوصندوق" }, "systemDefault": { - "message": "System default" + "message": "پیش‌فرض سیستم" }, "enterprisePolicyRequirementsApplied": { - "message": "Enterprise policy requirements have been applied to this setting" + "message": "الزامات سیاست سازمانی روی این تنظیم اعمال شده است" }, "sshPrivateKey": { - "message": "Private key" + "message": "کلید خصوصی" }, "sshPublicKey": { - "message": "Public key" + "message": "کلید عمومی" }, "sshFingerprint": { - "message": "Fingerprint" + "message": "اثر انگشت" }, "sshKeyAlgorithm": { - "message": "Key type" + "message": "نوع کلید" }, "sshKeyAlgorithmED25519": { "message": "ED25519" @@ -4852,61 +4961,61 @@ "message": "RSA 4096-Bit" }, "retry": { - "message": "Retry" + "message": "تلاش مجدد" }, "vaultCustomTimeoutMinimum": { - "message": "Minimum custom timeout is 1 minute." + "message": "حداقل مهلت زمانی سفارشی ۱ دقیقه است." }, "additionalContentAvailable": { - "message": "Additional content is available" + "message": "محتوای اضافی در دسترس است" }, "fileSavedToDevice": { - "message": "File saved to device. Manage from your device downloads." + "message": "پرونده در دستگاه ذخیره شد. از بخش بارگیری‌های دستگاه خود مدیریت کنید." }, "showCharacterCount": { - "message": "Show character count" + "message": "نمایش تعداد کاراکترها" }, "hideCharacterCount": { - "message": "Hide character count" + "message": "مخفی کردن تعداد کاراکترها" }, "itemsInTrash": { - "message": "Items in trash" + "message": "موراد در سطل زباله" }, "noItemsInTrash": { - "message": "No items in trash" + "message": "موردی در سطل زباله نیست" }, "noItemsInTrashDesc": { - "message": "Items you delete will appear here and be permanently deleted after 30 days" + "message": "مواردی که حذف می‌کنید اینجا نمایش داده می‌شوند و پس از ۳۰ روز به طور دائمی حذف خواهند شد" }, "trashWarning": { - "message": "Items that have been in trash more than 30 days will automatically be deleted" + "message": "مواردی که بیش از ۳۰ روز در سطل زباله بوده‌اند به‌طور خودکار حذف خواهند شد" }, "restore": { - "message": "Restore" + "message": "بازیابی" }, "deleteForever": { - "message": "Delete forever" + "message": "حذف برای همیشه" }, "noEditPermissions": { - "message": "You don't have permission to edit this item" + "message": "شما مجوز ویرایش این مورد را ندارید" }, "biometricsStatusHelptextUnlockNeeded": { - "message": "Biometric unlock is unavailable because PIN or password unlock is required first." + "message": "بازکردن با بیومتریک در دسترس نیست زیرا ابتدا باید بازکردن قفل با کد پین یا کلمه عبور انجام شود." }, "biometricsStatusHelptextHardwareUnavailable": { - "message": "Biometric unlock is currently unavailable." + "message": "باز کردن قفل با بیومتریک در حال حاضر در دسترس نیست." }, "biometricsStatusHelptextAutoSetupNeeded": { - "message": "Biometric unlock is unavailable due to misconfigured system files." + "message": "باز کردن قفل با بیومتریک به دلیل پیکربندی نادرست پرونده‌های سیستم در دسترس نیست." }, "biometricsStatusHelptextManualSetupNeeded": { - "message": "Biometric unlock is unavailable due to misconfigured system files." + "message": "باز کردن قفل با بیومتریک به دلیل پیکربندی نادرست پرونده‌های سیستم در دسترس نیست." }, "biometricsStatusHelptextDesktopDisconnected": { - "message": "Biometric unlock is unavailable because the Bitwarden desktop app is closed." + "message": "باز کردن قفل با بیومتریک در دسترس نیست زیرا برنامه دسکتاپ Bitwarden بسته است." }, "biometricsStatusHelptextNotEnabledInDesktop": { - "message": "Biometric unlock is unavailable because it is not enabled for $EMAIL$ in the Bitwarden desktop app.", + "message": "باز کردن قفل با بیومتریک در دسترس نیست زیرا برای $EMAIL$ در برنامه دسکتاپ Bitwarden فعال نشده است.", "placeholders": { "email": { "content": "$1", @@ -4915,244 +5024,217 @@ } }, "biometricsStatusHelptextUnavailableReasonUnknown": { - "message": "Biometric unlock is currently unavailable for an unknown reason." + "message": "باز کردن قفل با بیومتریک در حال حاضر به دلیل نامعلومی در دسترس نیست." + }, + "unlockVault": { + "message": "قفل گاوصندوق خود را در چند ثانیه باز کنید" + }, + "unlockVaultDesc": { + "message": "می‌توانید تنظیمات باز کردن قفل و زمان‌بندی خودکار بسته شدن گاوصندوق را سفارشی کنید تا سریع‌تر به گاوصندوق خود دسترسی پیدا کنید." + }, + "unlockPinSet": { + "message": "بازکردن قفل کد پین تنظیم شد" }, "authenticating": { - "message": "Authenticating" + "message": "در حال احراز هویت" }, "fillGeneratedPassword": { - "message": "Fill generated password", + "message": "پر کردن کلمه عبور تولید شده", "description": "Heading for the password generator within the inline menu" }, "passwordRegenerated": { - "message": "Password regenerated", + "message": "کلمه عبور تولید شد", "description": "Notification message for when a password has been regenerated" }, - "saveLoginToBitwarden": { - "message": "Save login to Bitwarden?", + "saveToBitwarden": { + "message": "ذخیره در Bitwarden", "description": "Confirmation message for saving a login to Bitwarden" }, "spaceCharacterDescriptor": { - "message": "Space", + "message": "فاصله", "description": "Represents the space key in screen reader content as a readable word" }, "tildeCharacterDescriptor": { - "message": "Tilde", + "message": "تیلد", "description": "Represents the ~ key in screen reader content as a readable word" }, "backtickCharacterDescriptor": { - "message": "Backtick", + "message": "بک‌تیک", "description": "Represents the ` key in screen reader content as a readable word" }, "exclamationCharacterDescriptor": { - "message": "Exclamation mark", + "message": "علامت تعجب", "description": "Represents the ! key in screen reader content as a readable word" }, "atSignCharacterDescriptor": { - "message": "At sign", + "message": "علامت @", "description": "Represents the @ key in screen reader content as a readable word" }, "hashSignCharacterDescriptor": { - "message": "Hash sign", + "message": "علامت #", "description": "Represents the # key in screen reader content as a readable word" }, "dollarSignCharacterDescriptor": { - "message": "Dollar sign", + "message": "علامت دلار", "description": "Represents the $ key in screen reader content as a readable word" }, "percentSignCharacterDescriptor": { - "message": "Percent sign", + "message": "علامت ٪", "description": "Represents the % key in screen reader content as a readable word" }, "caretCharacterDescriptor": { - "message": "Caret", + "message": "علامت ^", "description": "Represents the ^ key in screen reader content as a readable word" }, "ampersandCharacterDescriptor": { - "message": "Ampersand", + "message": "علامت &", "description": "Represents the & key in screen reader content as a readable word" }, "asteriskCharacterDescriptor": { - "message": "Asterisk", + "message": "علامت *", "description": "Represents the * key in screen reader content as a readable word" }, "parenLeftCharacterDescriptor": { - "message": "Left parenthesis", + "message": "پرانتز چپ", "description": "Represents the ( key in screen reader content as a readable word" }, "parenRightCharacterDescriptor": { - "message": "Right parenthesis", + "message": "پرانتز راست", "description": "Represents the ) key in screen reader content as a readable word" }, "hyphenCharacterDescriptor": { - "message": "Underscore", + "message": "خط زیر", "description": "Represents the _ key in screen reader content as a readable word" }, "underscoreCharacterDescriptor": { - "message": "Hyphen", + "message": "خط تیره", "description": "Represents the - key in screen reader content as a readable word" }, "plusCharacterDescriptor": { - "message": "Plus", + "message": "به علاوه", "description": "Represents the + key in screen reader content as a readable word" }, "equalsCharacterDescriptor": { - "message": "Equals", + "message": "مساوی", "description": "Represents the = key in screen reader content as a readable word" }, "braceLeftCharacterDescriptor": { - "message": "Left brace", + "message": "آکولادِ چپ", "description": "Represents the { key in screen reader content as a readable word" }, "braceRightCharacterDescriptor": { - "message": "Right brace", + "message": "آکولادِ راست", "description": "Represents the } key in screen reader content as a readable word" }, "bracketLeftCharacterDescriptor": { - "message": "Left bracket", + "message": "کروشه‌ی چپ", "description": "Represents the [ key in screen reader content as a readable word" }, "bracketRightCharacterDescriptor": { - "message": "Right bracket", + "message": "کروشه‌ی راست", "description": "Represents the ] key in screen reader content as a readable word" }, "pipeCharacterDescriptor": { - "message": "Pipe", + "message": "علامت |", "description": "Represents the | key in screen reader content as a readable word" }, "backSlashCharacterDescriptor": { - "message": "Back slash", + "message": "بک اسلش", "description": "Represents the back slash key in screen reader content as a readable word" }, "colonCharacterDescriptor": { - "message": "Colon", + "message": "دو نقطه", "description": "Represents the : key in screen reader content as a readable word" }, "semicolonCharacterDescriptor": { - "message": "Semicolon", + "message": "نقطه ویرگول", "description": "Represents the ; key in screen reader content as a readable word" }, "doubleQuoteCharacterDescriptor": { - "message": "Double quote", + "message": "دابل کوت", "description": "Represents the double quote key in screen reader content as a readable word" }, "singleQuoteCharacterDescriptor": { - "message": "Single quote", + "message": "سینگل کوت", "description": "Represents the ' key in screen reader content as a readable word" }, "lessThanCharacterDescriptor": { - "message": "Less than", + "message": "کمتر از", "description": "Represents the < key in screen reader content as a readable word" }, "greaterThanCharacterDescriptor": { - "message": "Greater than", + "message": "بیش‌تر از", "description": "Represents the > key in screen reader content as a readable word" }, "commaCharacterDescriptor": { - "message": "Comma", + "message": "کاما", "description": "Represents the , key in screen reader content as a readable word" }, "periodCharacterDescriptor": { - "message": "Period", + "message": "دوره", "description": "Represents the . key in screen reader content as a readable word" }, "questionCharacterDescriptor": { - "message": "Question mark", + "message": "علامت سوال", "description": "Represents the ? key in screen reader content as a readable word" }, "forwardSlashCharacterDescriptor": { - "message": "Forward slash", + "message": "ممیز", "description": "Represents the / key in screen reader content as a readable word" }, "lowercaseAriaLabel": { - "message": "Lowercase" + "message": "حروف کوچک" }, "uppercaseAriaLabel": { - "message": "Uppercase" + "message": "حروف بزرگ" }, "generatedPassword": { - "message": "Generated password" + "message": "کلمه عبور تولید شد" }, "compactMode": { - "message": "Compact mode" + "message": "حالت فشرده" }, "beta": { - "message": "Beta" - }, - "importantNotice": { - "message": "Important notice" - }, - "setupTwoStepLogin": { - "message": "Set up two-step login" - }, - "newDeviceVerificationNoticeContentPage1": { - "message": "Bitwarden will send a code to your account email to verify logins from new devices starting in February 2025." - }, - "newDeviceVerificationNoticeContentPage2": { - "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" - }, - "newDeviceVerificationNoticePageOneFormContent": { - "message": "Do you have reliable access to your email, $EMAIL$?", - "placeholders": { - "email": { - "content": "$1", - "example": "your_name@email.com" - } - } - }, - "newDeviceVerificationNoticePageOneEmailAccessNo": { - "message": "No, I do not" - }, - "newDeviceVerificationNoticePageOneEmailAccessYes": { - "message": "Yes, I can reliably access my email" - }, - "turnOnTwoStepLogin": { - "message": "Turn on two-step login" - }, - "changeAcctEmail": { - "message": "Change account email" + "message": "آزمایشی" }, "extensionWidth": { - "message": "Extension width" + "message": "عرض افزونه" }, "wide": { - "message": "Wide" + "message": "عریض" }, "extraWide": { - "message": "Extra wide" + "message": "خیلی عریض" }, "sshKeyWrongPassword": { - "message": "The password you entered is incorrect." + "message": "کلمه عبور وارد شده اشتباه است." }, "importSshKey": { - "message": "Import" + "message": "درون ریزی" }, "confirmSshKeyPassword": { - "message": "Confirm password" + "message": "تأیید کلمه عبور" }, "enterSshKeyPasswordDesc": { - "message": "Enter the password for the SSH key." + "message": "کلمه عبور کلید SSH را وارد کنید." }, "enterSshKeyPassword": { - "message": "Enter password" + "message": "کلمه عبور را وارد کنید" }, "invalidSshKey": { - "message": "The SSH key is invalid" + "message": "کلید SSH نامعتبر است" }, "sshKeyTypeUnsupported": { - "message": "The SSH key type is not supported" + "message": "نوع کلید SSH پشتیبانی نمی‌شود" }, "importSshKeyFromClipboard": { - "message": "Import key from clipboard" + "message": "وارد کردن کلید از حافظه موقت" }, "sshKeyImported": { - "message": "SSH key imported successfully" + "message": "کلید SSH با موفقیت وارد شد" }, "cannotRemoveViewOnlyCollections": { - "message": "You cannot remove collections with View only permissions: $COLLECTIONS$", + "message": "نمی‌توانید مجموعه‌هایی را که فقط دسترسی مشاهده دارند حذف کنید: $COLLECTIONS$", "placeholders": { "collections": { "content": "$1", @@ -5161,39 +5243,138 @@ } }, "updateDesktopAppOrDisableFingerprintDialogTitle": { - "message": "Please update your desktop application" + "message": "لطفاً برنامه دسکتاپ خود را به‌روزرسانی کنید" }, "updateDesktopAppOrDisableFingerprintDialogMessage": { - "message": "To use biometric unlock, please update your desktop application, or disable fingerprint unlock in the desktop settings." + "message": "برای استفاده از باز کردن قفل با بیومتریک، لطفاً برنامه دسکتاپ خود را به‌روزرسانی کنید یا باز کردن قفل با اثر انگشت را در تنظیمات دسکتاپ غیرفعال کنید." }, "changeAtRiskPassword": { - "message": "Change at-risk password" + "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 ذخیره کنید." + }, + "nudgeBadgeAria": { + "message": "۱ اعلان" + }, + "emptyVaultNudgeTitle": { + "message": "درون ریزی کلمات عبور موجود" + }, + "emptyVaultNudgeBody": { + "message": "از ابزار درون ریزی استفاده کنید تا ورودها را سریعاً به Bitwarden منتقل کنید بدون نیاز به افزودن دستی آن‌ها." + }, + "emptyVaultNudgeButton": { + "message": "الان درودن ریزی کن" + }, + "hasItemsVaultNudgeTitle": { + "message": "به گاوصندوق خود خوش آمدید!" + }, + "hasItemsVaultNudgeBodyOne": { + "message": "پر کردن خودکار موارد برای صفحه فعلی" + }, + "hasItemsVaultNudgeBodyTwo": { + "message": "موارد مورد علاقه برای دسترسی آسان" + }, + "hasItemsVaultNudgeBodyThree": { + "message": "در گاوصندوق خود به دنبال چیز دیگری بگردید" + }, + "newLoginNudgeTitle": { + "message": "با پر کردن خودکار در وقت خود صرفه جویی کنید" + }, + "newLoginNudgeBodyOne": { + "message": "شامل یک", + "description": "This is in multiple parts to allow for bold text in the middle of the sentence.", + "example": "Include a Website so this login appears as an autofill suggestion." + }, + "newLoginNudgeBodyBold": { + "message": "وب‌سایت", + "description": "This is in multiple parts to allow for bold text in the middle of the sentence.", + "example": "Include a Website so this login appears as an autofill suggestion." + }, + "newLoginNudgeBodyTwo": { + "message": "تا این ورود به عنوان پیشنهاد پر کردن خودکار ظاهر شود.", + "description": "This is in multiple parts to allow for bold text in the middle of the sentence.", + "example": "Include a Website so this login appears as an autofill suggestion." + }, + "newCardNudgeTitle": { + "message": "پرداخت آنلاین بدون وقفه" + }, + "newCardNudgeBody": { + "message": "با کارت‌ها، فرم‌های پرداخت را به‌راحتی و با امنیت و دقت پر کنید." + }, + "newIdentityNudgeTitle": { + "message": "ساخت حساب‌های کاربری را ساده کنید" + }, + "newIdentityNudgeBody": { + "message": "با هویت‌ها، به سرعت فرم‌های طولانی ثبت‌نام یا تماس را پر کنید." + }, + "newNoteNudgeTitle": { + "message": "اطلاعات حساس خود را ایمن نگه دارید" + }, + "newNoteNudgeBody": { + "message": "با یادداشت‌ها، اطلاعات حساسی مانند جزئیات بانکی یا بیمه را به‌صورت ایمن ذخیره کنید." + }, + "newSshNudgeTitle": { + "message": "دسترسی SSH مناسب برای توسعه‌دهندگان" + }, + "newSshNudgeBodyOne": { + "message": "کلیدهای خود را ذخیره کنید و با عامل SSH برای احراز هویت سریع و رمزگذاری‌شده متصل شوید.", + "description": "Two part message", + "example": "Store your keys and connect with the SSH agent for fast, encrypted authentication. Learn more about SSH agent" + }, + "newSshNudgeBodyTwo": { + "message": "اطلاعات بیشتر درباره عامل SSH را بیاموزید", + "description": "Two part message", + "example": "Store your keys and connect with the SSH agent for fast, encrypted authentication. Learn more about SSH agent" + }, + "generatorNudgeTitle": { + "message": "ساخت سریع کلمات عبور" + }, + "generatorNudgeBodyOne": { + "message": "به‌راحتی کلمات عبور قوی و منحصر به فرد ایجاد کنید با کلیک روی", + "description": "Two part message", + "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." + }, + "generatorNudgeBodyTwo": { + "message": "برای کمک به حفظ امنیت ورودهای شما.", + "description": "Two part message", + "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." + }, + "generatorNudgeBodyAria": { + "message": "با کلیک روی دکمه تولید رمز عبور، به‌راحتی کلمات عبور قوی و منحصر به‌ فرد ایجاد کنید تا ورودهای شما ایمن باقی بمانند.", + "description": "Aria label for the body content of the generator nudge" + }, + "noPermissionsViewPage": { + "message": "شما اجازه دسترسی به این صفحه را ندارید. لطفاً با حساب کاربری دیگری وارد شوید." } } diff --git a/apps/browser/src/_locales/fi/messages.json b/apps/browser/src/_locales/fi/messages.json index 4048925ebaa..ba2ed77c8de 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,55 @@ "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.", + "notificationNewItemAria": { + "message": "Uusi kohde, avautuu uudessa ikkunassa", + "description": "Aria label for the new item button in notification bar confirmation message when error is prompted" + }, + "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": { - "content": "$1" + "label": { + "content": "$1", + "example": "Login" } - }, - "description": "Shown to user after login is updated." + } + }, + "notificationLoginSaveConfirmation": { + "message": "saved to Bitwarden.", + "description": "Shown to user after item is saved." + }, + "notificationLoginUpdatedConfirmation": { + "message": "updated in Bitwarden.", + "description": "Shown to user after item is updated." + }, + "selectItemAriaLabel": { + "message": "Select $ITEMTYPE$, $ITEMNAME$", + "description": "Used by screen readers. $1 is the item type (like vault or folder), $2 is the selected item name.", + "placeholders": { + "itemType": { + "content": "$1" + }, + "itemName": { + "content": "$2" + } + } }, "saveAsNewLoginAction": { "message": "Tallenna uutena kirjautumistietona", @@ -1082,12 +1120,16 @@ "message": "Päivitä kirjautumistieto", "description": "Button text for updating an existing login entry." }, - "saveLoginPrompt": { - "message": "Tallennetaanko kirjautumistieto?", + "unlockToSave": { + "message": "Unlock to save this login", + "description": "User prompt to take action in order to save the login they just entered." + }, + "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 +1141,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 +1150,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 +1162,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": { @@ -1323,8 +1365,8 @@ "featureUnavailable": { "message": "Ominaisuus ei ole käytettävissä" }, - "encryptionKeyMigrationRequired": { - "message": "Salausavaimen siirto vaaditaan. Päivitä salausavaimesi kirjautumalla verkkoholviin." + "legacyEncryptionUnsupported": { + "message": "Legacy encryption is no longer supported. Please contact support to recover your account." }, "premiumMembership": { "message": "Premium-jäsenyys" @@ -1557,6 +1599,24 @@ "autofillSuggestionsSectionTitle": { "message": "Automaattitäytön ehdotukset" }, + "autofillSpotlightTitle": { + "message": "Easily find autofill suggestions" + }, + "autofillSpotlightDesc": { + "message": "Turn off your browser's autofill settings, so they don't conflict with Bitwarden." + }, + "turnOffBrowserAutofill": { + "message": "Poista automaattitäyttö käytöstä selaimessa $BROWSER$", + "placeholders": { + "browser": { + "content": "$1", + "example": "Chrome" + } + } + }, + "turnOffAutofill": { + "message": "Poista automaattitäyttö käytöstä" + }, "showInlineMenuLabel": { "message": "Näytä automaattitäytön ehdotukset lomakekentissä" }, @@ -2144,6 +2204,9 @@ "useThisPassword": { "message": "Käytä tätä salasanaa" }, + "useThisPassphrase": { + "message": "Use this passphrase" + }, "useThisUsername": { "message": "Käytä tätä käyttäjätunnusta" }, @@ -2167,15 +2230,6 @@ "vaultTimeoutAction1": { "message": "Aikakatkaisutoiminto" }, - "newCustomizationOptionsCalloutTitle": { - "message": "Uusia mukautusvaihtoehtoja" - }, - "newCustomizationOptionsCalloutContent": { - "message": "Mukauta holvikokemustasi pikakopiointitoiminnoilla, kompaktilla tilalla ja muulla!" - }, - "newCustomizationOptionsCalloutLink": { - "message": "Näytä kaikki ulkoasuasetukset" - }, "lock": { "message": "Lukitse", "description": "Verb form: to make secure or inaccessible by" @@ -2323,7 +2377,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 +2587,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 +2604,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" @@ -2623,6 +2677,10 @@ "message": "Kaikki Sendit", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, + "maxAccessCountReached": { + "message": "Max access count reached", + "description": "This text will be displayed after a Send has been accessed the maximum amount of times." + }, "hideTextByDefault": { "message": "Piilota teksti oletuksena" }, @@ -2963,14 +3021,14 @@ "copyCustomFieldNameNotUnique": { "message": "Yksilöllistä tunnistetta ei löytynyt." }, - "convertOrganizationEncryptionDesc": { - "message": "$ORGANIZATION$ käyttää kertakirjautumista (SSO) itse ylläpidetyllä avainpalvelimella. Organisaation jäsenet eivät enää tarvitse pääsalasanaa kirjautumiseen.", - "placeholders": { - "organization": { - "content": "$1", - "example": "My Org Name" - } - } + "removeMasterPasswordForOrganizationUserKeyConnector": { + "message": "A master password is no longer required for members of the following organization. Please confirm the domain below with your organization administrator." + }, + "organizationName": { + "message": "Organisaation nimi" + }, + "keyConnectorDomain": { + "message": "Key Connector domain" }, "leaveOrganization": { "message": "Poistu organisaatiosta" @@ -3006,7 +3064,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", @@ -3532,12 +3590,36 @@ "deviceTrusted": { "message": "Laitteeseen luotettu" }, - "sendsNoItemsTitle": { - "message": "Aktiivisia Sendejä ei ole", + "trustOrganization": { + "message": "Luota organisaatioon" + }, + "trust": { + "message": "Luota" + }, + "doNotTrust": { + "message": "Älä luota" + }, + "organizationNotTrusted": { + "message": "Organisaatio ei ole luotettu" + }, + "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" + }, + "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." + }, + "orgTrustWarning1": { + "message": "This organization has an Enterprise policy that will enroll you in account recovery. Enrollment will allow organization administrators to change your password. Only proceed if you recognize this organization and the fingerprint phrase displayed below matches the organization's fingerprint." + }, + "trustUser": { + "message": "Luota käyttäjään" + }, + "sendsTitleNoItems": { + "message": "Send sensitive information safely", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, - "sendsNoItemsMessage": { - "message": "Sendillä voit jakaa salattuja tietoja turvallisesti kenelle tahansa.", + "sendsBodyNoItems": { + "message": "Share files and data securely with anyone, on any platform. Your information will remain end-to-end encrypted while limiting exposure.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "inputRequired": { @@ -4272,7 +4354,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 +4378,7 @@ } }, "autofillTitleWithField": { - "message": "Autofill - $ITEMNAME$ - $FIELD$", + "message": "Automaattitäytä - $ITEMNAME$ - $FIELD$", "description": "Title for a button that autofills a login item.", "placeholders": { "itemname": { @@ -4456,6 +4538,33 @@ } } }, + "downloadBitwarden": { + "message": "Lataa Bitwarden" + }, + "downloadBitwardenOnAllDevices": { + "message": "Lataa Bitwarden kaikille laitteille" + }, + "getTheMobileApp": { + "message": "Hanki mobiilisovellus" + }, + "getTheMobileAppDesc": { + "message": "Access your passwords on the go with the Bitwarden mobile app." + }, + "getTheDesktopApp": { + "message": "Hanki työpöytäsovellus" + }, + "getTheDesktopAppDesc": { + "message": "Access your vault without a browser, then set up unlock with biometrics to expedite unlocking in both the desktop app and browser extension." + }, + "downloadFromBitwardenNow": { + "message": "Download from bitwarden.com now" + }, + "getItOnGooglePlay": { + "message": "Hanki se Google Playstä" + }, + "downloadOnTheAppStore": { + "message": "Lataa App Storesta" + }, "permanentlyDeleteAttachmentConfirmation": { "message": "Haluatko varmasti poistaa liitteen pysyvästi?" }, @@ -4917,6 +5026,15 @@ "biometricsStatusHelptextUnavailableReasonUnknown": { "message": "Biometrinen avaus ei ole tällä hetkellä käytettävissä tuntemattomasta syystä." }, + "unlockVault": { + "message": "Unlock your vault in seconds" + }, + "unlockVaultDesc": { + "message": "You can customize your unlock and timeout settings to more quickly access your vault." + }, + "unlockPinSet": { + "message": "Unlock PIN set" + }, "authenticating": { "message": "Todennetaan" }, @@ -4928,8 +5046,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": { @@ -5079,42 +5197,6 @@ "beta": { "message": "Beta" }, - "importantNotice": { - "message": "Tärkeä ilmoitus" - }, - "setupTwoStepLogin": { - "message": "Määritä kaksivaiheinen kirjautuminen" - }, - "newDeviceVerificationNoticeContentPage1": { - "message": "Bitwarden lähettää tilisi sähköpostiosoitteeseen koodin, jolla voit vahvistaa kirjautumiset uusista laitteista helmikuusta 2025 alkaen." - }, - "newDeviceVerificationNoticeContentPage2": { - "message": "Voit määrittää kaksivaiheisen kirjautumisen tilisi vaihtoehtoiseksi suojaustavaksi tai vaihtaa sähköpostiosoitteesi sellaiseen, johon sinulla on pääsy." - }, - "remindMeLater": { - "message": "Muistuta myöhemmin" - }, - "newDeviceVerificationNoticePageOneFormContent": { - "message": "Onko sinulla luotettava pääsy sähköpostiisi, $EMAIL$?", - "placeholders": { - "email": { - "content": "$1", - "example": "your_name@email.com" - } - } - }, - "newDeviceVerificationNoticePageOneEmailAccessNo": { - "message": "Ei ole" - }, - "newDeviceVerificationNoticePageOneEmailAccessYes": { - "message": "Kyllä on" - }, - "turnOnTwoStepLogin": { - "message": "Ota kaksivaiheinen kirjautuminen käyttöön" - }, - "changeAcctEmail": { - "message": "Muuta tilin sähköpostiosoitetta" - }, "extensionWidth": { "message": "Laajennuksen leveys" }, @@ -5169,31 +5251,130 @@ "changeAtRiskPassword": { "message": "Vaihda vaarantunut salasana" }, + "settingsVaultOptions": { + "message": "Holvin asetukset" + }, + "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." + }, + "nudgeBadgeAria": { + "message": "1 ilmoitus" + }, + "emptyVaultNudgeTitle": { + "message": "Tuo olemassa olevat salasanat" + }, + "emptyVaultNudgeBody": { + "message": "Use the importer to quickly transfer logins to Bitwarden without manually adding them." + }, + "emptyVaultNudgeButton": { + "message": "Tuo nyt" + }, + "hasItemsVaultNudgeTitle": { + "message": "Tervetuloa holviisi!" + }, + "hasItemsVaultNudgeBodyOne": { + "message": "Autofill items for the current page" + }, + "hasItemsVaultNudgeBodyTwo": { + "message": "Favorite items for easy access" + }, + "hasItemsVaultNudgeBodyThree": { + "message": "Search your vault for something else" + }, + "newLoginNudgeTitle": { + "message": "Säästä aikaa automaattitäytöllä" + }, + "newLoginNudgeBodyOne": { + "message": "Include a", + "description": "This is in multiple parts to allow for bold text in the middle of the sentence.", + "example": "Include a Website so this login appears as an autofill suggestion." + }, + "newLoginNudgeBodyBold": { + "message": "Verkkosivusto", + "description": "This is in multiple parts to allow for bold text in the middle of the sentence.", + "example": "Include a Website so this login appears as an autofill suggestion." + }, + "newLoginNudgeBodyTwo": { + "message": "so this login appears as an autofill suggestion.", + "description": "This is in multiple parts to allow for bold text in the middle of the sentence.", + "example": "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" + }, + "newSshNudgeBodyOne": { + "message": "Store your keys and connect with the SSH agent for fast, encrypted authentication.", + "description": "Two part message", + "example": "Store your keys and connect with the SSH agent for fast, encrypted authentication. Learn more about SSH agent" + }, + "newSshNudgeBodyTwo": { + "message": "Learn more about SSH agent", + "description": "Two part message", + "example": "Store your keys and connect with the SSH agent for fast, encrypted authentication. Learn more about SSH agent" + }, + "generatorNudgeTitle": { + "message": "Quickly create passwords" + }, + "generatorNudgeBodyOne": { + "message": "Easily create strong and unique passwords by clicking on", + "description": "Two part message", + "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." + }, + "generatorNudgeBodyTwo": { + "message": "to help you keep your logins secure.", + "description": "Two part message", + "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." + }, + "generatorNudgeBodyAria": { + "message": "Easily create strong and unique passwords by clicking on the Generate password button to help you keep your logins secure.", + "description": "Aria label for the body content of the generator nudge" + }, + "noPermissionsViewPage": { + "message": "You do not have permissions to view this page. Try logging in with a different account." } } diff --git a/apps/browser/src/_locales/fil/messages.json b/apps/browser/src/_locales/fil/messages.json index 9c3865bc0da..ce1a99debbe 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,55 @@ "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.", + "notificationNewItemAria": { + "message": "New Item, opens in new window", + "description": "Aria label for the new item button in notification bar confirmation message when error is prompted" + }, + "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": { - "content": "$1" + "label": { + "content": "$1", + "example": "Login" } - }, - "description": "Shown to user after login is updated." + } + }, + "notificationLoginSaveConfirmation": { + "message": "saved to Bitwarden.", + "description": "Shown to user after item is saved." + }, + "notificationLoginUpdatedConfirmation": { + "message": "updated in Bitwarden.", + "description": "Shown to user after item is updated." + }, + "selectItemAriaLabel": { + "message": "Select $ITEMTYPE$, $ITEMNAME$", + "description": "Used by screen readers. $1 is the item type (like vault or folder), $2 is the selected item name.", + "placeholders": { + "itemType": { + "content": "$1" + }, + "itemName": { + "content": "$2" + } + } }, "saveAsNewLoginAction": { "message": "Save as new login", @@ -1082,12 +1120,16 @@ "message": "Update login", "description": "Button text for updating an existing login entry." }, - "saveLoginPrompt": { - "message": "Save login?", + "unlockToSave": { + "message": "Unlock to save this login", + "description": "User prompt to take action in order to save the login they just entered." + }, + "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": { @@ -1323,8 +1365,8 @@ "featureUnavailable": { "message": "Hindi magagamit ang tampok" }, - "encryptionKeyMigrationRequired": { - "message": "Kinakailangan ang paglilipat ng encryption key. Mangyaring mag-login sa pamamagitan ng web vault upang i-update ang iyong encryption key." + "legacyEncryptionUnsupported": { + "message": "Legacy encryption is no longer supported. Please contact support to recover your account." }, "premiumMembership": { "message": "Pagiging miyembro ng premium" @@ -1557,6 +1599,24 @@ "autofillSuggestionsSectionTitle": { "message": "Autofill suggestions" }, + "autofillSpotlightTitle": { + "message": "Easily find autofill suggestions" + }, + "autofillSpotlightDesc": { + "message": "Turn off your browser's autofill settings, so they don't conflict with Bitwarden." + }, + "turnOffBrowserAutofill": { + "message": "Turn off $BROWSER$ autofill", + "placeholders": { + "browser": { + "content": "$1", + "example": "Chrome" + } + } + }, + "turnOffAutofill": { + "message": "Turn off autofill" + }, "showInlineMenuLabel": { "message": "Show autofill suggestions on form fields" }, @@ -2144,6 +2204,9 @@ "useThisPassword": { "message": "Use this password" }, + "useThisPassphrase": { + "message": "Use this passphrase" + }, "useThisUsername": { "message": "Use this username" }, @@ -2167,15 +2230,6 @@ "vaultTimeoutAction1": { "message": "Timeout action" }, - "newCustomizationOptionsCalloutTitle": { - "message": "New customization options" - }, - "newCustomizationOptionsCalloutContent": { - "message": "Customize your vault experience with quick copy actions, compact mode, and more!" - }, - "newCustomizationOptionsCalloutLink": { - "message": "View all Appearance settings" - }, "lock": { "message": "I-lock", "description": "Verb form: to make secure or inaccessible by" @@ -2623,6 +2677,10 @@ "message": "Lahat ng Mga Padala", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, + "maxAccessCountReached": { + "message": "Max access count reached", + "description": "This text will be displayed after a Send has been accessed the maximum amount of times." + }, "hideTextByDefault": { "message": "Hide text by default" }, @@ -2963,14 +3021,14 @@ "copyCustomFieldNameNotUnique": { "message": "Walang natagpuang natatanging nag-identipikar." }, - "convertOrganizationEncryptionDesc": { - "message": "$ORGANIZATION$ ay gumagamit ng SSO na may sariling-hosted na key server. Walang kinakailangang master password para mag-log in para sa mga miyembro ng organisasyon na ito.", - "placeholders": { - "organization": { - "content": "$1", - "example": "My Org Name" - } - } + "removeMasterPasswordForOrganizationUserKeyConnector": { + "message": "A master password is no longer required for members of the following organization. Please confirm the domain below with your organization administrator." + }, + "organizationName": { + "message": "Organization name" + }, + "keyConnectorDomain": { + "message": "Key Connector domain" }, "leaveOrganization": { "message": "Umalis sa organisasyon" @@ -3532,12 +3590,36 @@ "deviceTrusted": { "message": "Device trusted" }, - "sendsNoItemsTitle": { - "message": "No active Sends", + "trustOrganization": { + "message": "Trust organization" + }, + "trust": { + "message": "Trust" + }, + "doNotTrust": { + "message": "Do not trust" + }, + "organizationNotTrusted": { + "message": "Organization is not trusted" + }, + "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" + }, + "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." + }, + "orgTrustWarning1": { + "message": "This organization has an Enterprise policy that will enroll you in account recovery. Enrollment will allow organization administrators to change your password. Only proceed if you recognize this organization and the fingerprint phrase displayed below matches the organization's fingerprint." + }, + "trustUser": { + "message": "Trust user" + }, + "sendsTitleNoItems": { + "message": "Send sensitive information safely", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, - "sendsNoItemsMessage": { - "message": "Use Send to securely share encrypted information with anyone.", + "sendsBodyNoItems": { + "message": "Share files and data securely with anyone, on any platform. Your information will remain end-to-end encrypted while limiting exposure.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "inputRequired": { @@ -4456,6 +4538,33 @@ } } }, + "downloadBitwarden": { + "message": "Download Bitwarden" + }, + "downloadBitwardenOnAllDevices": { + "message": "Download Bitwarden on all devices" + }, + "getTheMobileApp": { + "message": "Get the mobile app" + }, + "getTheMobileAppDesc": { + "message": "Access your passwords on the go with the Bitwarden mobile app." + }, + "getTheDesktopApp": { + "message": "Get the desktop app" + }, + "getTheDesktopAppDesc": { + "message": "Access your vault without a browser, then set up unlock with biometrics to expedite unlocking in both the desktop app and browser extension." + }, + "downloadFromBitwardenNow": { + "message": "Download from bitwarden.com now" + }, + "getItOnGooglePlay": { + "message": "Get it on Google Play" + }, + "downloadOnTheAppStore": { + "message": "Download on the App Store" + }, "permanentlyDeleteAttachmentConfirmation": { "message": "Are you sure you want to permanently delete this attachment?" }, @@ -4917,6 +5026,15 @@ "biometricsStatusHelptextUnavailableReasonUnknown": { "message": "Biometric unlock is currently unavailable for an unknown reason." }, + "unlockVault": { + "message": "Unlock your vault in seconds" + }, + "unlockVaultDesc": { + "message": "You can customize your unlock and timeout settings to more quickly access your vault." + }, + "unlockPinSet": { + "message": "Unlock PIN set" + }, "authenticating": { "message": "Authenticating" }, @@ -4928,8 +5046,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": { @@ -5079,42 +5197,6 @@ "beta": { "message": "Beta" }, - "importantNotice": { - "message": "Important notice" - }, - "setupTwoStepLogin": { - "message": "Set up two-step login" - }, - "newDeviceVerificationNoticeContentPage1": { - "message": "Bitwarden will send a code to your account email to verify logins from new devices starting in February 2025." - }, - "newDeviceVerificationNoticeContentPage2": { - "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" - }, - "newDeviceVerificationNoticePageOneFormContent": { - "message": "Do you have reliable access to your email, $EMAIL$?", - "placeholders": { - "email": { - "content": "$1", - "example": "your_name@email.com" - } - } - }, - "newDeviceVerificationNoticePageOneEmailAccessNo": { - "message": "No, I do not" - }, - "newDeviceVerificationNoticePageOneEmailAccessYes": { - "message": "Yes, I can reliably access my email" - }, - "turnOnTwoStepLogin": { - "message": "Turn on two-step login" - }, - "changeAcctEmail": { - "message": "Change account email" - }, "extensionWidth": { "message": "Extension width" }, @@ -5169,6 +5251,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 +5283,98 @@ }, "secureDevicesBody": { "message": "Save unlimited passwords across unlimited devices with Bitwarden mobile, browser, and desktop apps." + }, + "nudgeBadgeAria": { + "message": "1 notification" + }, + "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!" + }, + "hasItemsVaultNudgeBodyOne": { + "message": "Autofill items for the current page" + }, + "hasItemsVaultNudgeBodyTwo": { + "message": "Favorite items for easy access" + }, + "hasItemsVaultNudgeBodyThree": { + "message": "Search your vault for something else" + }, + "newLoginNudgeTitle": { + "message": "Save time with autofill" + }, + "newLoginNudgeBodyOne": { + "message": "Include a", + "description": "This is in multiple parts to allow for bold text in the middle of the sentence.", + "example": "Include a Website so this login appears as an autofill suggestion." + }, + "newLoginNudgeBodyBold": { + "message": "Website", + "description": "This is in multiple parts to allow for bold text in the middle of the sentence.", + "example": "Include a Website so this login appears as an autofill suggestion." + }, + "newLoginNudgeBodyTwo": { + "message": "so this login appears as an autofill suggestion.", + "description": "This is in multiple parts to allow for bold text in the middle of the sentence.", + "example": "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" + }, + "newSshNudgeBodyOne": { + "message": "Store your keys and connect with the SSH agent for fast, encrypted authentication.", + "description": "Two part message", + "example": "Store your keys and connect with the SSH agent for fast, encrypted authentication. Learn more about SSH agent" + }, + "newSshNudgeBodyTwo": { + "message": "Learn more about SSH agent", + "description": "Two part message", + "example": "Store your keys and connect with the SSH agent for fast, encrypted authentication. Learn more about SSH agent" + }, + "generatorNudgeTitle": { + "message": "Quickly create passwords" + }, + "generatorNudgeBodyOne": { + "message": "Easily create strong and unique passwords by clicking on", + "description": "Two part message", + "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." + }, + "generatorNudgeBodyTwo": { + "message": "to help you keep your logins secure.", + "description": "Two part message", + "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." + }, + "generatorNudgeBodyAria": { + "message": "Easily create strong and unique passwords by clicking on the Generate password button to help you keep your logins secure.", + "description": "Aria label for the body content of the generator nudge" + }, + "noPermissionsViewPage": { + "message": "You do not have permissions to view this page. Try logging in with a different account." } } diff --git a/apps/browser/src/_locales/fr/messages.json b/apps/browser/src/_locales/fr/messages.json index 1f05f699ea9..f6ed8e79c30 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,55 @@ "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.", + "notificationNewItemAria": { + "message": "New Item, opens in new window", + "description": "Aria label for the new item button in notification bar confirmation message when error is prompted" + }, + "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": { - "content": "$1" + "label": { + "content": "$1", + "example": "Login" } - }, - "description": "Shown to user after login is updated." + } + }, + "notificationLoginSaveConfirmation": { + "message": "saved to Bitwarden.", + "description": "Shown to user after item is saved." + }, + "notificationLoginUpdatedConfirmation": { + "message": "updated in Bitwarden.", + "description": "Shown to user after item is updated." + }, + "selectItemAriaLabel": { + "message": "Select $ITEMTYPE$, $ITEMNAME$", + "description": "Used by screen readers. $1 is the item type (like vault or folder), $2 is the selected item name.", + "placeholders": { + "itemType": { + "content": "$1" + }, + "itemName": { + "content": "$2" + } + } }, "saveAsNewLoginAction": { "message": "Enregistrer en tant que nouvel identifiant", @@ -1082,12 +1120,16 @@ "message": "Mettre à jour l'identifiant", "description": "Button text for updating an existing login entry." }, - "saveLoginPrompt": { - "message": "Enregistrer l'identifiant ?", + "unlockToSave": { + "message": "Déverrouiller pour enregistrer l'identifiant", + "description": "User prompt to take action in order to save the login they just entered." + }, + "saveLogin": { + "message": "Enregistrer l'identifiant", "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": { @@ -1323,8 +1365,8 @@ "featureUnavailable": { "message": "Fonctionnalité indisponible" }, - "encryptionKeyMigrationRequired": { - "message": "Migration de la clé de chiffrement nécessaire. Veuillez vous connecter sur le coffre web pour mettre à jour votre clé de chiffrement." + "legacyEncryptionUnsupported": { + "message": "Legacy encryption is no longer supported. Please contact support to recover your account." }, "premiumMembership": { "message": "Adhésion Premium" @@ -1557,6 +1599,24 @@ "autofillSuggestionsSectionTitle": { "message": "Suggestions de saisie automatique" }, + "autofillSpotlightTitle": { + "message": "Easily find autofill suggestions" + }, + "autofillSpotlightDesc": { + "message": "Turn off your browser's autofill settings, so they don't conflict with Bitwarden." + }, + "turnOffBrowserAutofill": { + "message": "Turn off $BROWSER$ autofill", + "placeholders": { + "browser": { + "content": "$1", + "example": "Chrome" + } + } + }, + "turnOffAutofill": { + "message": "Désactiver la saisie automatique" + }, "showInlineMenuLabel": { "message": "Afficher les suggestions de saisie automatique dans les champs d'un formulaire" }, @@ -2144,6 +2204,9 @@ "useThisPassword": { "message": "Utiliser ce mot de passe" }, + "useThisPassphrase": { + "message": "Use this passphrase" + }, "useThisUsername": { "message": "Utiliser ce nom d'utilisateur" }, @@ -2167,15 +2230,6 @@ "vaultTimeoutAction1": { "message": "Expiration de l'action" }, - "newCustomizationOptionsCalloutTitle": { - "message": "Nouvelles options de personnalisation" - }, - "newCustomizationOptionsCalloutContent": { - "message": "Personnalisez votre expérience utilisateur du coffre avec des actions de copie rapide, un mode compact et bien plus encore !" - }, - "newCustomizationOptionsCalloutLink": { - "message": "Afficher tous les paramètres d'apparence" - }, "lock": { "message": "Verrouiller", "description": "Verb form: to make secure or inaccessible by" @@ -2323,7 +2377,7 @@ "message": "Politique de confidentialité" }, "yourNewPasswordCannotBeTheSameAsYourCurrentPassword": { - "message": "Your new password cannot be the same as your current password." + "message": "Votre nouveau mot de passe ne peut être le même que votre mot de passe actuel." }, "hintEqualsPassword": { "message": "Votre indice de mot de passe ne peut pas être identique à votre mot de passe." @@ -2623,6 +2677,10 @@ "message": "Tous les Sends", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, + "maxAccessCountReached": { + "message": "Max access count reached", + "description": "This text will be displayed after a Send has been accessed the maximum amount of times." + }, "hideTextByDefault": { "message": "Masquer le texte par défaut" }, @@ -2963,14 +3021,14 @@ "copyCustomFieldNameNotUnique": { "message": "Aucun identifiant unique trouvé." }, - "convertOrganizationEncryptionDesc": { - "message": "$ORGANIZATION$ utilise le SSO avec un serveur de clés auto-hébergé. Un mot de passe principal n'est plus nécessaire aux membres de cette organisation pour se connecter.", - "placeholders": { - "organization": { - "content": "$1", - "example": "My Org Name" - } - } + "removeMasterPasswordForOrganizationUserKeyConnector": { + "message": "A master password is no longer required for members of the following organization. Please confirm the domain below with your organization administrator." + }, + "organizationName": { + "message": "Nom de l'organisation" + }, + "keyConnectorDomain": { + "message": "Key Connector domain" }, "leaveOrganization": { "message": "Quitter l'organisation" @@ -3532,12 +3590,36 @@ "deviceTrusted": { "message": "Appareil de confiance" }, - "sendsNoItemsTitle": { - "message": "Pas de Send actif", + "trustOrganization": { + "message": "Trust organization" + }, + "trust": { + "message": "Faire confiance" + }, + "doNotTrust": { + "message": "Ne pas faire confiance" + }, + "organizationNotTrusted": { + "message": "Organization is not trusted" + }, + "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" + }, + "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." + }, + "orgTrustWarning1": { + "message": "This organization has an Enterprise policy that will enroll you in account recovery. Enrollment will allow organization administrators to change your password. Only proceed if you recognize this organization and the fingerprint phrase displayed below matches the organization's fingerprint." + }, + "trustUser": { + "message": "Trust user" + }, + "sendsTitleNoItems": { + "message": "Send sensitive information safely", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, - "sendsNoItemsMessage": { - "message": "Utilisez Send pour partager en toute sécurité des informations chiffrées avec tout le monde.", + "sendsBodyNoItems": { + "message": "Share files and data securely with anyone, on any platform. Your information will remain end-to-end encrypted while limiting exposure.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "inputRequired": { @@ -4456,6 +4538,33 @@ } } }, + "downloadBitwarden": { + "message": "Télécharger Bitwarden" + }, + "downloadBitwardenOnAllDevices": { + "message": "Télécharger Bitwarden sur tous les appareils" + }, + "getTheMobileApp": { + "message": "Télécharger l'application mobile" + }, + "getTheMobileAppDesc": { + "message": "Access your passwords on the go with the Bitwarden mobile app." + }, + "getTheDesktopApp": { + "message": "Télécharger l'application de bureau" + }, + "getTheDesktopAppDesc": { + "message": "Access your vault without a browser, then set up unlock with biometrics to expedite unlocking in both the desktop app and browser extension." + }, + "downloadFromBitwardenNow": { + "message": "Download from bitwarden.com now" + }, + "getItOnGooglePlay": { + "message": "Get it on Google Play" + }, + "downloadOnTheAppStore": { + "message": "Download on the App Store" + }, "permanentlyDeleteAttachmentConfirmation": { "message": "Êtes-vous sûr de vouloir supprimer définitivement cette pièce jointe ?" }, @@ -4917,6 +5026,15 @@ "biometricsStatusHelptextUnavailableReasonUnknown": { "message": "Le déverrouillage par biométrie n'est pas disponible actuellement pour une raison inconnue." }, + "unlockVault": { + "message": "Unlock your vault in seconds" + }, + "unlockVaultDesc": { + "message": "You can customize your unlock and timeout settings to more quickly access your vault." + }, + "unlockPinSet": { + "message": "Unlock PIN set" + }, "authenticating": { "message": "Authentification" }, @@ -4928,8 +5046,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": { @@ -5079,42 +5197,6 @@ "beta": { "message": "Beta" }, - "importantNotice": { - "message": "Avis important" - }, - "setupTwoStepLogin": { - "message": "Configurer l'identification à deux facteurs" - }, - "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." - }, - "newDeviceVerificationNoticeContentPage2": { - "message": "Vous pouvez configurer l'identification à deux facteurs comme un moyen alternatif de protéger votre compte ou de changer votre adresse courriel à une autre à laquelle vous pouvez accéder." - }, - "remindMeLater": { - "message": "Me le rappeler plus tard" - }, - "newDeviceVerificationNoticePageOneFormContent": { - "message": "Avez-vous un accès fiable à votre courriel $EMAIL$?", - "placeholders": { - "email": { - "content": "$1", - "example": "your_name@email.com" - } - } - }, - "newDeviceVerificationNoticePageOneEmailAccessNo": { - "message": "Non, je ne l'ai pas" - }, - "newDeviceVerificationNoticePageOneEmailAccessYes": { - "message": "Oui, je peux accéder à mon courriel de manière fiable" - }, - "turnOnTwoStepLogin": { - "message": "Activer l'identification à deux facteurs" - }, - "changeAcctEmail": { - "message": "Changer le courriel du compte" - }, "extensionWidth": { "message": "Largeur de l'extension" }, @@ -5169,6 +5251,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 +5283,98 @@ }, "secureDevicesBody": { "message": "Save unlimited passwords across unlimited devices with Bitwarden mobile, browser, and desktop apps." + }, + "nudgeBadgeAria": { + "message": "1 notification" + }, + "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!" + }, + "hasItemsVaultNudgeBodyOne": { + "message": "Autofill items for the current page" + }, + "hasItemsVaultNudgeBodyTwo": { + "message": "Favorite items for easy access" + }, + "hasItemsVaultNudgeBodyThree": { + "message": "Search your vault for something else" + }, + "newLoginNudgeTitle": { + "message": "Save time with autofill" + }, + "newLoginNudgeBodyOne": { + "message": "Include a", + "description": "This is in multiple parts to allow for bold text in the middle of the sentence.", + "example": "Include a Website so this login appears as an autofill suggestion." + }, + "newLoginNudgeBodyBold": { + "message": "Website", + "description": "This is in multiple parts to allow for bold text in the middle of the sentence.", + "example": "Include a Website so this login appears as an autofill suggestion." + }, + "newLoginNudgeBodyTwo": { + "message": "so this login appears as an autofill suggestion.", + "description": "This is in multiple parts to allow for bold text in the middle of the sentence.", + "example": "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" + }, + "newSshNudgeBodyOne": { + "message": "Store your keys and connect with the SSH agent for fast, encrypted authentication.", + "description": "Two part message", + "example": "Store your keys and connect with the SSH agent for fast, encrypted authentication. Learn more about SSH agent" + }, + "newSshNudgeBodyTwo": { + "message": "Learn more about SSH agent", + "description": "Two part message", + "example": "Store your keys and connect with the SSH agent for fast, encrypted authentication. Learn more about SSH agent" + }, + "generatorNudgeTitle": { + "message": "Quickly create passwords" + }, + "generatorNudgeBodyOne": { + "message": "Easily create strong and unique passwords by clicking on", + "description": "Two part message", + "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." + }, + "generatorNudgeBodyTwo": { + "message": "to help you keep your logins secure.", + "description": "Two part message", + "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." + }, + "generatorNudgeBodyAria": { + "message": "Easily create strong and unique passwords by clicking on the Generate password button to help you keep your logins secure.", + "description": "Aria label for the body content of the generator nudge" + }, + "noPermissionsViewPage": { + "message": "You do not have permissions to view this page. Try logging in with a different account." } } diff --git a/apps/browser/src/_locales/gl/messages.json b/apps/browser/src/_locales/gl/messages.json index b2b843d7830..4839eb6be81 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,55 @@ "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.", + "notificationNewItemAria": { + "message": "New Item, opens in new window", + "description": "Aria label for the new item button in notification bar confirmation message when error is prompted" + }, + "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": { - "content": "$1" + "label": { + "content": "$1", + "example": "Login" } - }, - "description": "Shown to user after login is updated." + } + }, + "notificationLoginSaveConfirmation": { + "message": "saved to Bitwarden.", + "description": "Shown to user after item is saved." + }, + "notificationLoginUpdatedConfirmation": { + "message": "updated in Bitwarden.", + "description": "Shown to user after item is updated." + }, + "selectItemAriaLabel": { + "message": "Select $ITEMTYPE$, $ITEMNAME$", + "description": "Used by screen readers. $1 is the item type (like vault or folder), $2 is the selected item name.", + "placeholders": { + "itemType": { + "content": "$1" + }, + "itemName": { + "content": "$2" + } + } }, "saveAsNewLoginAction": { "message": "Save as new login", @@ -1082,12 +1120,16 @@ "message": "Update login", "description": "Button text for updating an existing login entry." }, - "saveLoginPrompt": { - "message": "Save login?", + "unlockToSave": { + "message": "Unlock to save this login", + "description": "User prompt to take action in order to save the login they just entered." + }, + "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": { @@ -1323,8 +1365,8 @@ "featureUnavailable": { "message": "Función non dispoñible" }, - "encryptionKeyMigrationRequired": { - "message": "Requírese mudar a clave de cifrado. Por favor, inicia sesión na aplicación web para actualizala." + "legacyEncryptionUnsupported": { + "message": "Legacy encryption is no longer supported. Please contact support to recover your account." }, "premiumMembership": { "message": "Plan Prémium" @@ -1557,6 +1599,24 @@ "autofillSuggestionsSectionTitle": { "message": "Suxestións de autoenchido" }, + "autofillSpotlightTitle": { + "message": "Easily find autofill suggestions" + }, + "autofillSpotlightDesc": { + "message": "Turn off your browser's autofill settings, so they don't conflict with Bitwarden." + }, + "turnOffBrowserAutofill": { + "message": "Turn off $BROWSER$ autofill", + "placeholders": { + "browser": { + "content": "$1", + "example": "Chrome" + } + } + }, + "turnOffAutofill": { + "message": "Turn off autofill" + }, "showInlineMenuLabel": { "message": "Amosar suxestións de autoenchido en formularios" }, @@ -2144,6 +2204,9 @@ "useThisPassword": { "message": "Usar este contrasinal" }, + "useThisPassphrase": { + "message": "Use this passphrase" + }, "useThisUsername": { "message": "Usar este nome de usuario" }, @@ -2167,15 +2230,6 @@ "vaultTimeoutAction1": { "message": "Acción do temporizador" }, - "newCustomizationOptionsCalloutTitle": { - "message": "New customization options" - }, - "newCustomizationOptionsCalloutContent": { - "message": "Customize your vault experience with quick copy actions, compact mode, and more!" - }, - "newCustomizationOptionsCalloutLink": { - "message": "View all Appearance settings" - }, "lock": { "message": "Bloquear", "description": "Verb form: to make secure or inaccessible by" @@ -2623,6 +2677,10 @@ "message": "Todos os Sends", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, + "maxAccessCountReached": { + "message": "Max access count reached", + "description": "This text will be displayed after a Send has been accessed the maximum amount of times." + }, "hideTextByDefault": { "message": "Ocultar texto por defecto" }, @@ -2963,14 +3021,14 @@ "copyCustomFieldNameNotUnique": { "message": "Non se atopou ningún identificador único." }, - "convertOrganizationEncryptionDesc": { - "message": "$ORGANIZATION$ emprega SSO con un servidor de claves propio. Os membros da organización xa non precisan dun contrasinal mestre para iniciar sesión.", - "placeholders": { - "organization": { - "content": "$1", - "example": "My Org Name" - } - } + "removeMasterPasswordForOrganizationUserKeyConnector": { + "message": "A master password is no longer required for members of the following organization. Please confirm the domain below with your organization administrator." + }, + "organizationName": { + "message": "Organization name" + }, + "keyConnectorDomain": { + "message": "Key Connector domain" }, "leaveOrganization": { "message": "Deixar a organización" @@ -3532,12 +3590,36 @@ "deviceTrusted": { "message": "Dispositivo de confianza" }, - "sendsNoItemsTitle": { - "message": "Sen Sends activos", + "trustOrganization": { + "message": "Trust organization" + }, + "trust": { + "message": "Trust" + }, + "doNotTrust": { + "message": "Do not trust" + }, + "organizationNotTrusted": { + "message": "Organization is not trusted" + }, + "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" + }, + "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." + }, + "orgTrustWarning1": { + "message": "This organization has an Enterprise policy that will enroll you in account recovery. Enrollment will allow organization administrators to change your password. Only proceed if you recognize this organization and the fingerprint phrase displayed below matches the organization's fingerprint." + }, + "trustUser": { + "message": "Trust user" + }, + "sendsTitleNoItems": { + "message": "Send sensitive information safely", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, - "sendsNoItemsMessage": { - "message": "Usar send para compartir información cifrada con quen queiras.", + "sendsBodyNoItems": { + "message": "Share files and data securely with anyone, on any platform. Your information will remain end-to-end encrypted while limiting exposure.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "inputRequired": { @@ -4456,6 +4538,33 @@ } } }, + "downloadBitwarden": { + "message": "Download Bitwarden" + }, + "downloadBitwardenOnAllDevices": { + "message": "Download Bitwarden on all devices" + }, + "getTheMobileApp": { + "message": "Get the mobile app" + }, + "getTheMobileAppDesc": { + "message": "Access your passwords on the go with the Bitwarden mobile app." + }, + "getTheDesktopApp": { + "message": "Get the desktop app" + }, + "getTheDesktopAppDesc": { + "message": "Access your vault without a browser, then set up unlock with biometrics to expedite unlocking in both the desktop app and browser extension." + }, + "downloadFromBitwardenNow": { + "message": "Download from bitwarden.com now" + }, + "getItOnGooglePlay": { + "message": "Get it on Google Play" + }, + "downloadOnTheAppStore": { + "message": "Download on the App Store" + }, "permanentlyDeleteAttachmentConfirmation": { "message": "Estás seguro de que queres eliminar permanentemente este anexo?" }, @@ -4917,6 +5026,15 @@ "biometricsStatusHelptextUnavailableReasonUnknown": { "message": "O desbloqueo biométrico non está dispoñible por algunha razón non prevista." }, + "unlockVault": { + "message": "Unlock your vault in seconds" + }, + "unlockVaultDesc": { + "message": "You can customize your unlock and timeout settings to more quickly access your vault." + }, + "unlockPinSet": { + "message": "Unlock PIN set" + }, "authenticating": { "message": "Autenticando" }, @@ -4928,8 +5046,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": { @@ -5079,42 +5197,6 @@ "beta": { "message": "Beta" }, - "importantNotice": { - "message": "Aviso importante" - }, - "setupTwoStepLogin": { - "message": "Configurar verificación en dous pasos" - }, - "newDeviceVerificationNoticeContentPage1": { - "message": "A partir de febreiro de 2025 Bitwarden comezará a enviar correos con códigos de verificación para confirmar novos inicios de sesión á túa conta." - }, - "newDeviceVerificationNoticeContentPage2": { - "message": "Podes configurar a verificación en 2 pasos como alternativa para protexer a túa conta ou cambiar o enderezo electrónico a un ó que teñas acceso." - }, - "remindMeLater": { - "message": "Lembrarmo máis tarde" - }, - "newDeviceVerificationNoticePageOneFormContent": { - "message": "Tes acceso fiable ó teu correo? ($EMAIL$)", - "placeholders": { - "email": { - "content": "$1", - "example": "your_name@email.com" - } - } - }, - "newDeviceVerificationNoticePageOneEmailAccessNo": { - "message": "Non, non o teño" - }, - "newDeviceVerificationNoticePageOneEmailAccessYes": { - "message": "Si, teño acceso fiable ó meu correo" - }, - "turnOnTwoStepLogin": { - "message": "Activar verificación en dous pasos" - }, - "changeAcctEmail": { - "message": "Mudar de correo electrónico" - }, "extensionWidth": { "message": "Ancho da extensión" }, @@ -5169,6 +5251,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 +5283,98 @@ }, "secureDevicesBody": { "message": "Save unlimited passwords across unlimited devices with Bitwarden mobile, browser, and desktop apps." + }, + "nudgeBadgeAria": { + "message": "1 notification" + }, + "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!" + }, + "hasItemsVaultNudgeBodyOne": { + "message": "Autofill items for the current page" + }, + "hasItemsVaultNudgeBodyTwo": { + "message": "Favorite items for easy access" + }, + "hasItemsVaultNudgeBodyThree": { + "message": "Search your vault for something else" + }, + "newLoginNudgeTitle": { + "message": "Save time with autofill" + }, + "newLoginNudgeBodyOne": { + "message": "Include a", + "description": "This is in multiple parts to allow for bold text in the middle of the sentence.", + "example": "Include a Website so this login appears as an autofill suggestion." + }, + "newLoginNudgeBodyBold": { + "message": "Website", + "description": "This is in multiple parts to allow for bold text in the middle of the sentence.", + "example": "Include a Website so this login appears as an autofill suggestion." + }, + "newLoginNudgeBodyTwo": { + "message": "so this login appears as an autofill suggestion.", + "description": "This is in multiple parts to allow for bold text in the middle of the sentence.", + "example": "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" + }, + "newSshNudgeBodyOne": { + "message": "Store your keys and connect with the SSH agent for fast, encrypted authentication.", + "description": "Two part message", + "example": "Store your keys and connect with the SSH agent for fast, encrypted authentication. Learn more about SSH agent" + }, + "newSshNudgeBodyTwo": { + "message": "Learn more about SSH agent", + "description": "Two part message", + "example": "Store your keys and connect with the SSH agent for fast, encrypted authentication. Learn more about SSH agent" + }, + "generatorNudgeTitle": { + "message": "Quickly create passwords" + }, + "generatorNudgeBodyOne": { + "message": "Easily create strong and unique passwords by clicking on", + "description": "Two part message", + "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." + }, + "generatorNudgeBodyTwo": { + "message": "to help you keep your logins secure.", + "description": "Two part message", + "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." + }, + "generatorNudgeBodyAria": { + "message": "Easily create strong and unique passwords by clicking on the Generate password button to help you keep your logins secure.", + "description": "Aria label for the body content of the generator nudge" + }, + "noPermissionsViewPage": { + "message": "You do not have permissions to view this page. Try logging in with a different account." } } diff --git a/apps/browser/src/_locales/he/messages.json b/apps/browser/src/_locales/he/messages.json index fd38df53906..7cb09c3b4fc 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" + }, "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,55 @@ "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.", + "notificationNewItemAria": { + "message": "New Item, opens in new window", + "description": "Aria label for the new item button in notification bar confirmation message when error is prompted" + }, + "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": { - "content": "$1" + "label": { + "content": "$1", + "example": "Login" } - }, - "description": "Shown to user after login is updated." + } + }, + "notificationLoginSaveConfirmation": { + "message": "saved to Bitwarden.", + "description": "Shown to user after item is saved." + }, + "notificationLoginUpdatedConfirmation": { + "message": "updated in Bitwarden.", + "description": "Shown to user after item is updated." + }, + "selectItemAriaLabel": { + "message": "Select $ITEMTYPE$, $ITEMNAME$", + "description": "Used by screen readers. $1 is the item type (like vault or folder), $2 is the selected item name.", + "placeholders": { + "itemType": { + "content": "$1" + }, + "itemName": { + "content": "$2" + } + } }, "saveAsNewLoginAction": { "message": "שמור ככניסה חדשה", @@ -1082,12 +1120,16 @@ "message": "עדכן כניסה", "description": "Button text for updating an existing login entry." }, - "saveLoginPrompt": { - "message": "לשמור כניסה?", + "unlockToSave": { + "message": "Unlock to save this login", + "description": "User prompt to take action in order to save the login they just entered." + }, + "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": { @@ -1323,8 +1365,8 @@ "featureUnavailable": { "message": "התכונה אינה זמינה" }, - "encryptionKeyMigrationRequired": { - "message": "נדרשת הגירת מפתח הצפנה. נא להיכנס דרך כספת הרשת כדי לעדכן את מפתח ההצפנה שלך." + "legacyEncryptionUnsupported": { + "message": "Legacy encryption is no longer supported. Please contact support to recover your account." }, "premiumMembership": { "message": "חברות פרימיום" @@ -1557,6 +1599,24 @@ "autofillSuggestionsSectionTitle": { "message": "הצעות למילוי אוטומטי" }, + "autofillSpotlightTitle": { + "message": "Easily find autofill suggestions" + }, + "autofillSpotlightDesc": { + "message": "Turn off your browser's autofill settings, so they don't conflict with Bitwarden." + }, + "turnOffBrowserAutofill": { + "message": "Turn off $BROWSER$ autofill", + "placeholders": { + "browser": { + "content": "$1", + "example": "Chrome" + } + } + }, + "turnOffAutofill": { + "message": "Turn off autofill" + }, "showInlineMenuLabel": { "message": "הצג הצעות למילוי אוטומטי על שדות טופס" }, @@ -2144,6 +2204,9 @@ "useThisPassword": { "message": "השתמש בסיסמה זו" }, + "useThisPassphrase": { + "message": "Use this passphrase" + }, "useThisUsername": { "message": "השתמש בשם משתמש זה" }, @@ -2167,15 +2230,6 @@ "vaultTimeoutAction1": { "message": "פעולת פסק זמן" }, - "newCustomizationOptionsCalloutTitle": { - "message": "אפשרויות התאמה אישית חדשות" - }, - "newCustomizationOptionsCalloutContent": { - "message": "התאם אישית את חווית הכספת שלך עם פעולות העתקה מהירות, מצב קומפקטי, ועוד!" - }, - "newCustomizationOptionsCalloutLink": { - "message": "הצג את כל הגדרות המראה" - }, "lock": { "message": "נעילה", "description": "Verb form: to make secure or inaccessible by" @@ -2623,6 +2677,10 @@ "message": "כל הסֵנְדים", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, + "maxAccessCountReached": { + "message": "מספר הגישות המרבי הושג", + "description": "This text will be displayed after a Send has been accessed the maximum amount of times." + }, "hideTextByDefault": { "message": "הסתר טקסט כברירת מחדל" }, @@ -2963,14 +3021,14 @@ "copyCustomFieldNameNotUnique": { "message": "לא נמצא מזהה ייחודי." }, - "convertOrganizationEncryptionDesc": { - "message": "$ORGANIZATION$ משתמש/ת ב־SSO עם שרת מפתחות באירוח עצמי. סיסמה ראשית כבר לא נדרשת כדי להיכנס עבור חברים של ארגון זה.", - "placeholders": { - "organization": { - "content": "$1", - "example": "My Org Name" - } - } + "removeMasterPasswordForOrganizationUserKeyConnector": { + "message": "A master password is no longer required for members of the following organization. Please confirm the domain below with your organization administrator." + }, + "organizationName": { + "message": "Organization name" + }, + "keyConnectorDomain": { + "message": "Key Connector domain" }, "leaveOrganization": { "message": "עזוב ארגון" @@ -3532,12 +3590,36 @@ "deviceTrusted": { "message": "מכשיר מהימן" }, - "sendsNoItemsTitle": { - "message": "אין סֵנְדים פעילים", + "trustOrganization": { + "message": "Trust organization" + }, + "trust": { + "message": "Trust" + }, + "doNotTrust": { + "message": "Do not trust" + }, + "organizationNotTrusted": { + "message": "Organization is not trusted" + }, + "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" + }, + "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." + }, + "orgTrustWarning1": { + "message": "This organization has an Enterprise policy that will enroll you in account recovery. Enrollment will allow organization administrators to change your password. Only proceed if you recognize this organization and the fingerprint phrase displayed below matches the organization's fingerprint." + }, + "trustUser": { + "message": "Trust user" + }, + "sendsTitleNoItems": { + "message": "Send sensitive information safely", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, - "sendsNoItemsMessage": { - "message": "השתמש בסֵנְד כדי לשתף באופן מאובטח מידע מוצפן עם כל אחד.", + "sendsBodyNoItems": { + "message": "Share files and data securely with anyone, on any platform. Your information will remain end-to-end encrypted while limiting exposure.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "inputRequired": { @@ -4456,6 +4538,33 @@ } } }, + "downloadBitwarden": { + "message": "Download Bitwarden" + }, + "downloadBitwardenOnAllDevices": { + "message": "Download Bitwarden on all devices" + }, + "getTheMobileApp": { + "message": "Get the mobile app" + }, + "getTheMobileAppDesc": { + "message": "Access your passwords on the go with the Bitwarden mobile app." + }, + "getTheDesktopApp": { + "message": "Get the desktop app" + }, + "getTheDesktopAppDesc": { + "message": "Access your vault without a browser, then set up unlock with biometrics to expedite unlocking in both the desktop app and browser extension." + }, + "downloadFromBitwardenNow": { + "message": "Download from bitwarden.com now" + }, + "getItOnGooglePlay": { + "message": "Get it on Google Play" + }, + "downloadOnTheAppStore": { + "message": "Download on the App Store" + }, "permanentlyDeleteAttachmentConfirmation": { "message": "האם אתה בטוח שברצונך למחוק לצמיתות צרופה זו?" }, @@ -4917,6 +5026,15 @@ "biometricsStatusHelptextUnavailableReasonUnknown": { "message": "ביטול נעילה ביומטרי אינו זמין כעת מסיבה לא ידועה." }, + "unlockVault": { + "message": "Unlock your vault in seconds" + }, + "unlockVaultDesc": { + "message": "You can customize your unlock and timeout settings to more quickly access your vault." + }, + "unlockPinSet": { + "message": "Unlock PIN set" + }, "authenticating": { "message": "מאמת" }, @@ -4928,8 +5046,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": { @@ -5079,42 +5197,6 @@ "beta": { "message": "בטא" }, - "importantNotice": { - "message": "הודעה חשובה" - }, - "setupTwoStepLogin": { - "message": "הגדר כניסה דו־שלבית" - }, - "newDeviceVerificationNoticeContentPage1": { - "message": "Bitwarden ישלח קוד לדוא\"ל החשבון שלך כדי לאמת כניסות ממכשירים חדשים החל מפברואר 2025." - }, - "newDeviceVerificationNoticeContentPage2": { - "message": "אתה יכול להגדיר כניסה דו־שלבית כדרך חלופית להגן על החשבון שלך או לשנות את הדוא\"ל שלך לאחד שאתה יכול לגשת אליו." - }, - "remindMeLater": { - "message": "הזכר לי מאוחר יותר" - }, - "newDeviceVerificationNoticePageOneFormContent": { - "message": "יש לך גישה מהימנה לדוא\"ל שלך, $EMAIL$?", - "placeholders": { - "email": { - "content": "$1", - "example": "your_name@email.com" - } - } - }, - "newDeviceVerificationNoticePageOneEmailAccessNo": { - "message": "לא, אין לי" - }, - "newDeviceVerificationNoticePageOneEmailAccessYes": { - "message": "כן, אני יכול לגשת לדוא\"ל שלי באופן מהימן" - }, - "turnOnTwoStepLogin": { - "message": "הפעל כניסה דו־שלבית" - }, - "changeAcctEmail": { - "message": "שנה את דוא\"ל החשבון" - }, "extensionWidth": { "message": "רוחב הרחבה" }, @@ -5169,6 +5251,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 +5283,98 @@ }, "secureDevicesBody": { "message": "Save unlimited passwords across unlimited devices with Bitwarden mobile, browser, and desktop apps." + }, + "nudgeBadgeAria": { + "message": "1 notification" + }, + "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!" + }, + "hasItemsVaultNudgeBodyOne": { + "message": "Autofill items for the current page" + }, + "hasItemsVaultNudgeBodyTwo": { + "message": "Favorite items for easy access" + }, + "hasItemsVaultNudgeBodyThree": { + "message": "Search your vault for something else" + }, + "newLoginNudgeTitle": { + "message": "Save time with autofill" + }, + "newLoginNudgeBodyOne": { + "message": "Include a", + "description": "This is in multiple parts to allow for bold text in the middle of the sentence.", + "example": "Include a Website so this login appears as an autofill suggestion." + }, + "newLoginNudgeBodyBold": { + "message": "Website", + "description": "This is in multiple parts to allow for bold text in the middle of the sentence.", + "example": "Include a Website so this login appears as an autofill suggestion." + }, + "newLoginNudgeBodyTwo": { + "message": "so this login appears as an autofill suggestion.", + "description": "This is in multiple parts to allow for bold text in the middle of the sentence.", + "example": "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" + }, + "newSshNudgeBodyOne": { + "message": "Store your keys and connect with the SSH agent for fast, encrypted authentication.", + "description": "Two part message", + "example": "Store your keys and connect with the SSH agent for fast, encrypted authentication. Learn more about SSH agent" + }, + "newSshNudgeBodyTwo": { + "message": "Learn more about SSH agent", + "description": "Two part message", + "example": "Store your keys and connect with the SSH agent for fast, encrypted authentication. Learn more about SSH agent" + }, + "generatorNudgeTitle": { + "message": "Quickly create passwords" + }, + "generatorNudgeBodyOne": { + "message": "Easily create strong and unique passwords by clicking on", + "description": "Two part message", + "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." + }, + "generatorNudgeBodyTwo": { + "message": "to help you keep your logins secure.", + "description": "Two part message", + "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." + }, + "generatorNudgeBodyAria": { + "message": "Easily create strong and unique passwords by clicking on the Generate password button to help you keep your logins secure.", + "description": "Aria label for the body content of the generator nudge" + }, + "noPermissionsViewPage": { + "message": "You do not have permissions to view this page. Try logging in with a different account." } } diff --git a/apps/browser/src/_locales/hi/messages.json b/apps/browser/src/_locales/hi/messages.json index 813b69b9079..8531f9a481a 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,55 @@ "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$ को बिटवार्डन में अपडेट किया गया।", + "notificationNewItemAria": { + "message": "New Item, opens in new window", + "description": "Aria label for the new item button in notification bar confirmation message when error is prompted" + }, + "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": { - "content": "$1" + "label": { + "content": "$1", + "example": "Login" } - }, - "description": "Shown to user after login is updated." + } + }, + "notificationLoginSaveConfirmation": { + "message": "saved to Bitwarden.", + "description": "Shown to user after item is saved." + }, + "notificationLoginUpdatedConfirmation": { + "message": "updated in Bitwarden.", + "description": "Shown to user after item is updated." + }, + "selectItemAriaLabel": { + "message": "Select $ITEMTYPE$, $ITEMNAME$", + "description": "Used by screen readers. $1 is the item type (like vault or folder), $2 is the selected item name.", + "placeholders": { + "itemType": { + "content": "$1" + }, + "itemName": { + "content": "$2" + } + } }, "saveAsNewLoginAction": { "message": "Save as new login", @@ -1082,12 +1120,16 @@ "message": "Update login", "description": "Button text for updating an existing login entry." }, - "saveLoginPrompt": { - "message": "Save login?", + "unlockToSave": { + "message": "Unlock to save this login", + "description": "User prompt to take action in order to save the login they just entered." + }, + "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": { @@ -1323,8 +1365,8 @@ "featureUnavailable": { "message": "Feature Unavailable" }, - "encryptionKeyMigrationRequired": { - "message": "Encryption key migration required. Please login through the web vault to update your encryption key." + "legacyEncryptionUnsupported": { + "message": "Legacy encryption is no longer supported. Please contact support to recover your account." }, "premiumMembership": { "message": "Premium Membership" @@ -1557,6 +1599,24 @@ "autofillSuggestionsSectionTitle": { "message": "Autofill suggestions" }, + "autofillSpotlightTitle": { + "message": "Easily find autofill suggestions" + }, + "autofillSpotlightDesc": { + "message": "Turn off your browser's autofill settings, so they don't conflict with Bitwarden." + }, + "turnOffBrowserAutofill": { + "message": "Turn off $BROWSER$ autofill", + "placeholders": { + "browser": { + "content": "$1", + "example": "Chrome" + } + } + }, + "turnOffAutofill": { + "message": "Turn off autofill" + }, "showInlineMenuLabel": { "message": "Show autofill suggestions on form fields" }, @@ -2144,6 +2204,9 @@ "useThisPassword": { "message": "Use this password" }, + "useThisPassphrase": { + "message": "Use this passphrase" + }, "useThisUsername": { "message": "Use this username" }, @@ -2167,15 +2230,6 @@ "vaultTimeoutAction1": { "message": "टाइमआउट कार्रवाई" }, - "newCustomizationOptionsCalloutTitle": { - "message": "New customization options" - }, - "newCustomizationOptionsCalloutContent": { - "message": "Customize your vault experience with quick copy actions, compact mode, and more!" - }, - "newCustomizationOptionsCalloutLink": { - "message": "View all Appearance settings" - }, "lock": { "message": "लॉक", "description": "Verb form: to make secure or inaccessible by" @@ -2623,6 +2677,10 @@ "message": "सभी Sends", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, + "maxAccessCountReached": { + "message": "Max access count reached", + "description": "This text will be displayed after a Send has been accessed the maximum amount of times." + }, "hideTextByDefault": { "message": "Hide text by default" }, @@ -2963,14 +3021,14 @@ "copyCustomFieldNameNotUnique": { "message": "No unique identifier found." }, - "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.", - "placeholders": { - "organization": { - "content": "$1", - "example": "My Org Name" - } - } + "removeMasterPasswordForOrganizationUserKeyConnector": { + "message": "A master password is no longer required for members of the following organization. Please confirm the domain below with your organization administrator." + }, + "organizationName": { + "message": "Organization name" + }, + "keyConnectorDomain": { + "message": "Key Connector domain" }, "leaveOrganization": { "message": "Leave organization" @@ -3532,12 +3590,36 @@ "deviceTrusted": { "message": "Device trusted" }, - "sendsNoItemsTitle": { - "message": "No active Sends", + "trustOrganization": { + "message": "विश्वसनीय संगठन" + }, + "trust": { + "message": "भरोसा करें" + }, + "doNotTrust": { + "message": "भरोसा न करें" + }, + "organizationNotTrusted": { + "message": "संगठन पर भरोसा नहीं है" + }, + "emergencyAccessTrustWarning": { + "message": "अपने खाते की सुरक्षा के लिए, केवल तभी पुष्टि करें जब आपने इस उपयोगकर्ता को आपातकालीन पहुँच प्रदान की हो और उनका फिंगरप्रिंट उनके खाते में प्रदर्शित फिंगरप्रिंट से मेल खाता हो" + }, + "orgTrustWarning": { + "message": "अपने खाते की सुरक्षा के लिए, केवल तभी आगे बढ़ें जब आप इस संगठन के सदस्य हों, खाता पुनर्प्राप्ति सक्षम हो, तथा नीचे प्रदर्शित फिंगरप्रिंट संगठन के फिंगरप्रिंट से मेल खाता हो।" + }, + "orgTrustWarning1": { + "message": "This organization has an Enterprise policy that will enroll you in account recovery. Enrollment will allow organization administrators to change your password. Only proceed if you recognize this organization and the fingerprint phrase displayed below matches the organization's fingerprint." + }, + "trustUser": { + "message": "उपयोगकर्ता पर भरोसा रखें" + }, + "sendsTitleNoItems": { + "message": "Send sensitive information safely", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, - "sendsNoItemsMessage": { - "message": "Use Send to securely share encrypted information with anyone.", + "sendsBodyNoItems": { + "message": "Share files and data securely with anyone, on any platform. Your information will remain end-to-end encrypted while limiting exposure.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "inputRequired": { @@ -4456,6 +4538,33 @@ } } }, + "downloadBitwarden": { + "message": "Download Bitwarden" + }, + "downloadBitwardenOnAllDevices": { + "message": "Download Bitwarden on all devices" + }, + "getTheMobileApp": { + "message": "Get the mobile app" + }, + "getTheMobileAppDesc": { + "message": "Access your passwords on the go with the Bitwarden mobile app." + }, + "getTheDesktopApp": { + "message": "Get the desktop app" + }, + "getTheDesktopAppDesc": { + "message": "Access your vault without a browser, then set up unlock with biometrics to expedite unlocking in both the desktop app and browser extension." + }, + "downloadFromBitwardenNow": { + "message": "Download from bitwarden.com now" + }, + "getItOnGooglePlay": { + "message": "Get it on Google Play" + }, + "downloadOnTheAppStore": { + "message": "Download on the App Store" + }, "permanentlyDeleteAttachmentConfirmation": { "message": "Are you sure you want to permanently delete this attachment?" }, @@ -4917,6 +5026,15 @@ "biometricsStatusHelptextUnavailableReasonUnknown": { "message": "Biometric unlock is currently unavailable for an unknown reason." }, + "unlockVault": { + "message": "Unlock your vault in seconds" + }, + "unlockVaultDesc": { + "message": "You can customize your unlock and timeout settings to more quickly access your vault." + }, + "unlockPinSet": { + "message": "Unlock PIN set" + }, "authenticating": { "message": "Authenticating" }, @@ -4928,8 +5046,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": { @@ -5079,42 +5197,6 @@ "beta": { "message": "Beta" }, - "importantNotice": { - "message": "महत्वपूर्ण सूचना" - }, - "setupTwoStepLogin": { - "message": "टू-स्टेप लॉगइन" - }, - "newDeviceVerificationNoticeContentPage1": { - "message": "Bitwarden will send a code to your account email to verify logins from new devices starting in February 2025." - }, - "newDeviceVerificationNoticeContentPage2": { - "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": "बाद में मुझे याद कराएं" - }, - "newDeviceVerificationNoticePageOneFormContent": { - "message": "Do you have reliable access to your email, $EMAIL$?", - "placeholders": { - "email": { - "content": "$1", - "example": "your_name@email.com" - } - } - }, - "newDeviceVerificationNoticePageOneEmailAccessNo": { - "message": "नहीं, मैं नहीं" - }, - "newDeviceVerificationNoticePageOneEmailAccessYes": { - "message": "हाँ, मैं आराम से अपना ईमेल देख सकता हूँ" - }, - "turnOnTwoStepLogin": { - "message": "टू-स्टेप लॉगइन सक्षम करें" - }, - "changeAcctEmail": { - "message": "अकाउंट का ईमेल बदलें" - }, "extensionWidth": { "message": "Extension width" }, @@ -5169,6 +5251,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 +5283,98 @@ }, "secureDevicesBody": { "message": "Save unlimited passwords across unlimited devices with Bitwarden mobile, browser, and desktop apps." + }, + "nudgeBadgeAria": { + "message": "1 notification" + }, + "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!" + }, + "hasItemsVaultNudgeBodyOne": { + "message": "Autofill items for the current page" + }, + "hasItemsVaultNudgeBodyTwo": { + "message": "Favorite items for easy access" + }, + "hasItemsVaultNudgeBodyThree": { + "message": "Search your vault for something else" + }, + "newLoginNudgeTitle": { + "message": "स्वतः भरण से समय बचाएँ" + }, + "newLoginNudgeBodyOne": { + "message": "Include a", + "description": "This is in multiple parts to allow for bold text in the middle of the sentence.", + "example": "Include a Website so this login appears as an autofill suggestion." + }, + "newLoginNudgeBodyBold": { + "message": "Website", + "description": "This is in multiple parts to allow for bold text in the middle of the sentence.", + "example": "Include a Website so this login appears as an autofill suggestion." + }, + "newLoginNudgeBodyTwo": { + "message": "so this login appears as an autofill suggestion.", + "description": "This is in multiple parts to allow for bold text in the middle of the sentence.", + "example": "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" + }, + "newSshNudgeBodyOne": { + "message": "Store your keys and connect with the SSH agent for fast, encrypted authentication.", + "description": "Two part message", + "example": "Store your keys and connect with the SSH agent for fast, encrypted authentication. Learn more about SSH agent" + }, + "newSshNudgeBodyTwo": { + "message": "Learn more about SSH agent", + "description": "Two part message", + "example": "Store your keys and connect with the SSH agent for fast, encrypted authentication. Learn more about SSH agent" + }, + "generatorNudgeTitle": { + "message": "Quickly create passwords" + }, + "generatorNudgeBodyOne": { + "message": "Easily create strong and unique passwords by clicking on", + "description": "Two part message", + "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." + }, + "generatorNudgeBodyTwo": { + "message": "to help you keep your logins secure.", + "description": "Two part message", + "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." + }, + "generatorNudgeBodyAria": { + "message": "Easily create strong and unique passwords by clicking on the Generate password button to help you keep your logins secure.", + "description": "Aria label for the body content of the generator nudge" + }, + "noPermissionsViewPage": { + "message": "You do not have permissions to view this page. Try logging in with a different account." } } diff --git a/apps/browser/src/_locales/hr/messages.json b/apps/browser/src/_locales/hr/messages.json index 896593332a4..d585e8ec3ff 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,55 @@ "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.", + "notificationNewItemAria": { + "message": "New Item, opens in new window", + "description": "Aria label for the new item button in notification bar confirmation message when error is prompted" + }, + "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": { - "content": "$1" + "label": { + "content": "$1", + "example": "Login" } - }, - "description": "Shown to user after login is updated." + } + }, + "notificationLoginSaveConfirmation": { + "message": "saved to Bitwarden.", + "description": "Shown to user after item is saved." + }, + "notificationLoginUpdatedConfirmation": { + "message": "updated in Bitwarden.", + "description": "Shown to user after item is updated." + }, + "selectItemAriaLabel": { + "message": "Select $ITEMTYPE$, $ITEMNAME$", + "description": "Used by screen readers. $1 is the item type (like vault or folder), $2 is the selected item name.", + "placeholders": { + "itemType": { + "content": "$1" + }, + "itemName": { + "content": "$2" + } + } }, "saveAsNewLoginAction": { "message": "Spremi novu prijavu", @@ -1082,12 +1120,16 @@ "message": "Ažuriraj prijavu", "description": "Button text for updating an existing login entry." }, - "saveLoginPrompt": { - "message": "Spremiti prijavu?", + "unlockToSave": { + "message": "Unlock to save this login", + "description": "User prompt to take action in order to save the login they just entered." + }, + "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": { @@ -1323,8 +1365,8 @@ "featureUnavailable": { "message": "Značajka nije dostupna" }, - "encryptionKeyMigrationRequired": { - "message": "Potrebna je migracija ključa za šifriranje. Prijavi se na web trezoru za ažuriranje ključa za šifriranje." + "legacyEncryptionUnsupported": { + "message": "Legacy encryption is no longer supported. Please contact support to recover your account." }, "premiumMembership": { "message": "Premium članstvo" @@ -1557,6 +1599,24 @@ "autofillSuggestionsSectionTitle": { "message": "Prijedlozi auto-ispune" }, + "autofillSpotlightTitle": { + "message": "Easily find autofill suggestions" + }, + "autofillSpotlightDesc": { + "message": "Turn off your browser's autofill settings, so they don't conflict with Bitwarden." + }, + "turnOffBrowserAutofill": { + "message": "Turn off $BROWSER$ autofill", + "placeholders": { + "browser": { + "content": "$1", + "example": "Chrome" + } + } + }, + "turnOffAutofill": { + "message": "Turn off autofill" + }, "showInlineMenuLabel": { "message": "Prikaži prijedloge auto-ispune na poljima obrazaca" }, @@ -2144,6 +2204,9 @@ "useThisPassword": { "message": "Koristi ovu lozinku" }, + "useThisPassphrase": { + "message": "Use this passphrase" + }, "useThisUsername": { "message": "Koristi ovo korisničko ime" }, @@ -2167,15 +2230,6 @@ "vaultTimeoutAction1": { "message": "Radnja nakon isteka " }, - "newCustomizationOptionsCalloutTitle": { - "message": "Nove mogućnosti prilagodbe" - }, - "newCustomizationOptionsCalloutContent": { - "message": "Prilagodi svoje iskustvo trezora brzim kopiranjem, kompaktnim načinom rada i više!" - }, - "newCustomizationOptionsCalloutLink": { - "message": "Pogledaj sve postavke izgleda" - }, "lock": { "message": "Zaključaj", "description": "Verb form: to make secure or inaccessible by" @@ -2623,6 +2677,10 @@ "message": "Svi Sendovi", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, + "maxAccessCountReached": { + "message": "Max access count reached", + "description": "This text will be displayed after a Send has been accessed the maximum amount of times." + }, "hideTextByDefault": { "message": "Zadano sakrij tekst" }, @@ -2963,14 +3021,14 @@ "copyCustomFieldNameNotUnique": { "message": "Nije nađen jedinstveni identifikator." }, - "convertOrganizationEncryptionDesc": { - "message": "$ORGANIZATION$ koristi jedinstvenu prijavu SSO s vlastitim poslužiteljem. Članovima organizacije glavna lozinka više nije potrebna.", - "placeholders": { - "organization": { - "content": "$1", - "example": "My Org Name" - } - } + "removeMasterPasswordForOrganizationUserKeyConnector": { + "message": "A master password is no longer required for members of the following organization. Please confirm the domain below with your organization administrator." + }, + "organizationName": { + "message": "Organization name" + }, + "keyConnectorDomain": { + "message": "Key Connector domain" }, "leaveOrganization": { "message": "Napusti organizaciju" @@ -3532,12 +3590,36 @@ "deviceTrusted": { "message": "Uređaj pouzdan" }, - "sendsNoItemsTitle": { - "message": "Nema aktivnih Sendova", + "trustOrganization": { + "message": "Trust organization" + }, + "trust": { + "message": "Trust" + }, + "doNotTrust": { + "message": "Do not trust" + }, + "organizationNotTrusted": { + "message": "Organization is not trusted" + }, + "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" + }, + "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." + }, + "orgTrustWarning1": { + "message": "This organization has an Enterprise policy that will enroll you in account recovery. Enrollment will allow organization administrators to change your password. Only proceed if you recognize this organization and the fingerprint phrase displayed below matches the organization's fingerprint." + }, + "trustUser": { + "message": "Trust user" + }, + "sendsTitleNoItems": { + "message": "Send sensitive information safely", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, - "sendsNoItemsMessage": { - "message": "Koristi Send za sigurno slanje šifriranih podataka bilo kome.", + "sendsBodyNoItems": { + "message": "Share files and data securely with anyone, on any platform. Your information will remain end-to-end encrypted while limiting exposure.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "inputRequired": { @@ -4456,6 +4538,33 @@ } } }, + "downloadBitwarden": { + "message": "Download Bitwarden" + }, + "downloadBitwardenOnAllDevices": { + "message": "Download Bitwarden on all devices" + }, + "getTheMobileApp": { + "message": "Get the mobile app" + }, + "getTheMobileAppDesc": { + "message": "Access your passwords on the go with the Bitwarden mobile app." + }, + "getTheDesktopApp": { + "message": "Get the desktop app" + }, + "getTheDesktopAppDesc": { + "message": "Access your vault without a browser, then set up unlock with biometrics to expedite unlocking in both the desktop app and browser extension." + }, + "downloadFromBitwardenNow": { + "message": "Download from bitwarden.com now" + }, + "getItOnGooglePlay": { + "message": "Get it on Google Play" + }, + "downloadOnTheAppStore": { + "message": "Download on the App Store" + }, "permanentlyDeleteAttachmentConfirmation": { "message": "Sigurno želiš trajno izbrisati ovaj privitak?" }, @@ -4917,6 +5026,15 @@ "biometricsStatusHelptextUnavailableReasonUnknown": { "message": "Biometrijsko otključavanje trenutno nije dostupno iz nepoznatog razloga." }, + "unlockVault": { + "message": "Unlock your vault in seconds" + }, + "unlockVaultDesc": { + "message": "You can customize your unlock and timeout settings to more quickly access your vault." + }, + "unlockPinSet": { + "message": "Unlock PIN set" + }, "authenticating": { "message": "Autentifikacija" }, @@ -4928,8 +5046,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": { @@ -5079,42 +5197,6 @@ "beta": { "message": "Beta" }, - "importantNotice": { - "message": "Važna napomena" - }, - "setupTwoStepLogin": { - "message": "Postavi dvostruku autentifikaciju" - }, - "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." - }, - "newDeviceVerificationNoticeContentPage2": { - "message": "Prijavu dvostrukom autentifikacijom možeš postaviti kao alternativni način zaštite svog računa ili promijeni svoju e-poštu u onu kojoj možeš pristupiti." - }, - "remindMeLater": { - "message": "Podsjeti me kasnije" - }, - "newDeviceVerificationNoticePageOneFormContent": { - "message": "Imaš li pouzdan pristup svojoj e-pošti: $EMAIL$?", - "placeholders": { - "email": { - "content": "$1", - "example": "your_name@email.com" - } - } - }, - "newDeviceVerificationNoticePageOneEmailAccessNo": { - "message": "Ne, nemam" - }, - "newDeviceVerificationNoticePageOneEmailAccessYes": { - "message": "Da, pouzdano mogu pristupiti svojoj e-pošti" - }, - "turnOnTwoStepLogin": { - "message": "Uključi prijavu dvostrukom autentifikacijom" - }, - "changeAcctEmail": { - "message": "Promjeni e-poštu računa" - }, "extensionWidth": { "message": "Širina proširenja" }, @@ -5169,6 +5251,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 +5283,98 @@ }, "secureDevicesBody": { "message": "Save unlimited passwords across unlimited devices with Bitwarden mobile, browser, and desktop apps." + }, + "nudgeBadgeAria": { + "message": "1 notification" + }, + "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!" + }, + "hasItemsVaultNudgeBodyOne": { + "message": "Autofill items for the current page" + }, + "hasItemsVaultNudgeBodyTwo": { + "message": "Favorite items for easy access" + }, + "hasItemsVaultNudgeBodyThree": { + "message": "Search your vault for something else" + }, + "newLoginNudgeTitle": { + "message": "Save time with autofill" + }, + "newLoginNudgeBodyOne": { + "message": "Include a", + "description": "This is in multiple parts to allow for bold text in the middle of the sentence.", + "example": "Include a Website so this login appears as an autofill suggestion." + }, + "newLoginNudgeBodyBold": { + "message": "Website", + "description": "This is in multiple parts to allow for bold text in the middle of the sentence.", + "example": "Include a Website so this login appears as an autofill suggestion." + }, + "newLoginNudgeBodyTwo": { + "message": "so this login appears as an autofill suggestion.", + "description": "This is in multiple parts to allow for bold text in the middle of the sentence.", + "example": "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" + }, + "newSshNudgeBodyOne": { + "message": "Store your keys and connect with the SSH agent for fast, encrypted authentication.", + "description": "Two part message", + "example": "Store your keys and connect with the SSH agent for fast, encrypted authentication. Learn more about SSH agent" + }, + "newSshNudgeBodyTwo": { + "message": "Learn more about SSH agent", + "description": "Two part message", + "example": "Store your keys and connect with the SSH agent for fast, encrypted authentication. Learn more about SSH agent" + }, + "generatorNudgeTitle": { + "message": "Quickly create passwords" + }, + "generatorNudgeBodyOne": { + "message": "Easily create strong and unique passwords by clicking on", + "description": "Two part message", + "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." + }, + "generatorNudgeBodyTwo": { + "message": "to help you keep your logins secure.", + "description": "Two part message", + "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." + }, + "generatorNudgeBodyAria": { + "message": "Easily create strong and unique passwords by clicking on the Generate password button to help you keep your logins secure.", + "description": "Aria label for the body content of the generator nudge" + }, + "noPermissionsViewPage": { + "message": "You do not have permissions to view this page. Try logging in with a different account." } } diff --git a/apps/browser/src/_locales/hu/messages.json b/apps/browser/src/_locales/hu/messages.json index f58e542c77f..73232437bc6 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,55 @@ "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.", + "notificationNewItemAria": { + "message": "Új elem, megnyitás új ablakban", + "description": "Aria label for the new item button in notification bar confirmation message when error is prompted" + }, + "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": { - "content": "$1" + "label": { + "content": "$1", + "example": "Login" } - }, - "description": "Shown to user after login is updated." + } + }, + "notificationLoginSaveConfirmation": { + "message": "mentésre került a Bitwardenbe.", + "description": "Shown to user after item is saved." + }, + "notificationLoginUpdatedConfirmation": { + "message": "frissítésre került a Bitwardenben.", + "description": "Shown to user after item is updated." + }, + "selectItemAriaLabel": { + "message": "$ITEMTYPE$, $ITEMNAME$ választás", + "description": "Used by screen readers. $1 is the item type (like vault or folder), $2 is the selected item name.", + "placeholders": { + "itemType": { + "content": "$1" + }, + "itemName": { + "content": "$2" + } + } }, "saveAsNewLoginAction": { "message": "Mentés új bejelentkezésként", @@ -1082,12 +1120,16 @@ "message": "Bejelentkezés frissítése", "description": "Button text for updating an existing login entry." }, - "saveLoginPrompt": { - "message": "Bejelentkezés mentése?", + "unlockToSave": { + "message": "Feloldás a bejelentkezés mentéséhez", + "description": "User prompt to take action in order to save the login they just entered." + }, + "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": { @@ -1323,8 +1365,8 @@ "featureUnavailable": { "message": "A funkció nem érhető el." }, - "encryptionKeyMigrationRequired": { - "message": "Titkosítási kulcs migráció szükséges. Jelentkezzünk be a webes széfen keresztül a titkosítási kulcs frissítéséhez." + "legacyEncryptionUnsupported": { + "message": "A régi titkosítás már nem támogatott. Lépjünk kapcsolatba a támogatással a fiók helyreállításához." }, "premiumMembership": { "message": "Prémium tagság" @@ -1557,6 +1599,24 @@ "autofillSuggestionsSectionTitle": { "message": "Autofill suggestions" }, + "autofillSpotlightTitle": { + "message": "Az automatikus kitöltési javaslatok könnyű megtalálása" + }, + "autofillSpotlightDesc": { + "message": "Kapcsoljuk ki a böngésző automatikus kitöltési beállításait, így azok nem ütköznek a Bitwardennel." + }, + "turnOffBrowserAutofill": { + "message": "$BROWSER$ automatikus kitöltés kikapcsolása", + "placeholders": { + "browser": { + "content": "$1", + "example": "Chrome" + } + } + }, + "turnOffAutofill": { + "message": "Automat kitöltés bekapcsolása" + }, "showInlineMenuLabel": { "message": "Show autofill suggestions on form fields" }, @@ -2144,6 +2204,9 @@ "useThisPassword": { "message": "Jelszó használata" }, + "useThisPassphrase": { + "message": "Jelmondat használata" + }, "useThisUsername": { "message": "Felhasználónév használata" }, @@ -2167,15 +2230,6 @@ "vaultTimeoutAction1": { "message": "Időkifutási művelet" }, - "newCustomizationOptionsCalloutTitle": { - "message": "Új testreszabási opciók" - }, - "newCustomizationOptionsCalloutContent": { - "message": "Szabjuk testre tárhely élményét gyors másolási műveletekkel, kompakt móddal és még sok mással!" - }, - "newCustomizationOptionsCalloutLink": { - "message": "Az összes megjelenési beállítás megtekintése" - }, "lock": { "message": "Lezárás", "description": "Verb form: to make secure or inaccessible by" @@ -2623,6 +2677,10 @@ "message": "Összes Send", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, + "maxAccessCountReached": { + "message": "A maximális hozzáférések száma elérésre került.", + "description": "This text will be displayed after a Send has been accessed the maximum amount of times." + }, "hideTextByDefault": { "message": "Szöveg elrejtése alapértelmezetten" }, @@ -2963,14 +3021,14 @@ "copyCustomFieldNameNotUnique": { "message": "Nincs egyedi azonosító." }, - "convertOrganizationEncryptionDesc": { - "message": "$ORGANIZATION$ jelenleg saját tárolású aláíráskulcsú SSO szervert használ. A mesterjelszó a továbbiakban nem szükséges a szervezeti tagsági bejelentkezéshez.", - "placeholders": { - "organization": { - "content": "$1", - "example": "My Org Name" - } - } + "removeMasterPasswordForOrganizationUserKeyConnector": { + "message": "A következő szervezet tagjai számára már nincs szükség mesterjelszóra. Erősítsük meg az alábbi tartományt a szervezet adminisztrátorával." + }, + "organizationName": { + "message": "Szervezet neve" + }, + "keyConnectorDomain": { + "message": "Key Connector tartomány" }, "leaveOrganization": { "message": "Szervezet elhagyása" @@ -3532,12 +3590,36 @@ "deviceTrusted": { "message": "Az eszköz megbízható." }, - "sendsNoItemsTitle": { - "message": "Nincsenek natív Send elemek.", + "trustOrganization": { + "message": "Bizalmi szervezet" + }, + "trust": { + "message": "Bizalom" + }, + "doNotTrust": { + "message": "Nincs bizalom" + }, + "organizationNotTrusted": { + "message": "A szervezet nem megbízható." + }, + "emergencyAccessTrustWarning": { + "message": "A fiók biztonság érdekében csak akkor erősítsük meg, ha vészhelyzeti hozzáférést biztosítottunk ehhez a felhasználóhoz és az ujjlenyomata megegyezik a fiókjukban megjelenítettekkel." + }, + "orgTrustWarning": { + "message": "A fiók biztonsága érdekében csak akkor folytassuk, ha tagja vagyunk ennek a szervezetnek, engedélyezve van a fiók helyreállítása és az alább megjelenített ujjlenyomat megegyezik a szervezet ujjlenyomatával." + }, + "orgTrustWarning1": { + "message": "Ennek a szervezetnek van egy vállalati szabályzata, amely regisztrál bennünket a fiók helyreállítási szolgáltatásba. A regisztráció lehetővé teszi a szervezet rendszergazdái számára, hogy megváltoztassák a jelszavunkat. Csak akkor folytassuk, ha felismerjük ezt a szervezetet és az alább megjelenített ujjlenyomat-kifejezés megegyezik a szervezet ujjlenyomatával." + }, + "trustUser": { + "message": "Megbízható felhasználó" + }, + "sendsTitleNoItems": { + "message": "Érzékeny információt küldése biztonságosan", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, - "sendsNoItemsMessage": { - "message": "A Send használatával biztonságosan megoszthatjuk a titkosított információkat bárkivel.", + "sendsBodyNoItems": { + "message": "Fájlok vagy adatok megosztása biztonságosan bárkivel, bármilyen platformon. Az információk titkosítva maradnak a végpontokon, korlátozva a kitettséget.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "inputRequired": { @@ -4456,6 +4538,33 @@ } } }, + "downloadBitwarden": { + "message": "Bitwarden letöltése" + }, + "downloadBitwardenOnAllDevices": { + "message": "Bitwarden letöltése minden eszközön" + }, + "getTheMobileApp": { + "message": "Mobilalkalmazás beszerzése" + }, + "getTheMobileAppDesc": { + "message": "Útközben is hozzáférhetünk a jelszabkhoz a Bitwarden mobilalkalmazással." + }, + "getTheDesktopApp": { + "message": "Asztali alkalmazás beszerzése" + }, + "getTheDesktopAppDesc": { + "message": "Hozzáférhetünk a széfhez böngésző nélkül, majd állítsuk be a feloldást biometrikus adatokkal, hogy felgyorsítsuk a feloldást mind az asztali alkalmazásban, mind a böngésző bővítményben." + }, + "downloadFromBitwardenNow": { + "message": "Letöltés most a bitwarden.com webhelyről" + }, + "getItOnGooglePlay": { + "message": "Beszerzés a Google Playen" + }, + "downloadOnTheAppStore": { + "message": "Letöltés az App Store-ból" + }, "permanentlyDeleteAttachmentConfirmation": { "message": "Biztosan véglegesen törlésre kerüljön ez a melléklet?" }, @@ -4917,6 +5026,15 @@ "biometricsStatusHelptextUnavailableReasonUnknown": { "message": "A biometrikus feloldás jelenleg ismeretlen okból nem érhető el." }, + "unlockVault": { + "message": "A széf feloldása másodpercek alatt" + }, + "unlockVaultDesc": { + "message": "Testreszabhatjuk a feloldási és időtúllépési beállításokat a széf gyorsabb elérése érdekében." + }, + "unlockPinSet": { + "message": "PIN beállítás feloldása" + }, "authenticating": { "message": "Authenticating" }, @@ -4928,8 +5046,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": { @@ -5079,42 +5197,6 @@ "beta": { "message": "Béta" }, - "importantNotice": { - "message": "Fontos megjegyzés" - }, - "setupTwoStepLogin": { - "message": "Kétlépéses bejelentkezés beüzemelése" - }, - "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." - }, - "newDeviceVerificationNoticeContentPage2": { - "message": "A fiók védelmének alternatív módjaként beállíthatunk kétlépcsős bejelentkezést vagy módosíthatjuk az email címet egy elérhetőre." - }, - "remindMeLater": { - "message": "Emlékeztetés később" - }, - "newDeviceVerificationNoticePageOneFormContent": { - "message": "Megbízható a hozzáférés $EMAIL$ email címhez?", - "placeholders": { - "email": { - "content": "$1", - "example": "your_name@email.com" - } - } - }, - "newDeviceVerificationNoticePageOneEmailAccessNo": { - "message": "Nem, nem érem el" - }, - "newDeviceVerificationNoticePageOneEmailAccessYes": { - "message": "Igen, megbízhatóan hozzáférek az emailjeimhez" - }, - "turnOnTwoStepLogin": { - "message": "Kétlépéses bejelentkezés bekapcsolása" - }, - "changeAcctEmail": { - "message": "Fiók email cím megváltoztatása" - }, "extensionWidth": { "message": "Kiterjesztés szélesség" }, @@ -5169,6 +5251,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 +5283,98 @@ }, "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." + }, + "nudgeBadgeAria": { + "message": "1 értesítés" + }, + "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!" + }, + "hasItemsVaultNudgeBodyOne": { + "message": "Az aktuális oldal elemeinek automatikus kitöltése" + }, + "hasItemsVaultNudgeBodyTwo": { + "message": "Kedvenc elemek a könnyű hozzáférés érdekében" + }, + "hasItemsVaultNudgeBodyThree": { + "message": "Valami más keresése a széfben" + }, + "newLoginNudgeTitle": { + "message": "Idő megtakarítás automatikus kitöltéssel" + }, + "newLoginNudgeBodyOne": { + "message": "Bevonás:", + "description": "This is in multiple parts to allow for bold text in the middle of the sentence.", + "example": "Include a Website so this login appears as an autofill suggestion." + }, + "newLoginNudgeBodyBold": { + "message": "Webhely", + "description": "This is in multiple parts to allow for bold text in the middle of the sentence.", + "example": "Include a Website so this login appears as an autofill suggestion." + }, + "newLoginNudgeBodyTwo": { + "message": "így ez a bejelentkezés automatikus kitöltési javaslatként jelenik meg.", + "description": "This is in multiple parts to allow for bold text in the middle of the sentence.", + "example": "Include a Website so this login appears as an autofill suggestion." + }, + "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" + }, + "newSshNudgeBodyOne": { + "message": "Tároljuk el a kulcsokat és csatlakozzunk az SSH ügynökhöz a gyors, titkosított hitelesítéshez.", + "description": "Two part message", + "example": "Store your keys and connect with the SSH agent for fast, encrypted authentication. Learn more about SSH agent" + }, + "newSshNudgeBodyTwo": { + "message": "További információ az SSH ügynökről", + "description": "Two part message", + "example": "Store your keys and connect with the SSH agent for fast, encrypted authentication. Learn more about SSH agent" + }, + "generatorNudgeTitle": { + "message": "Jelszavak gyors létrehozása" + }, + "generatorNudgeBodyOne": { + "message": "Könnyen létrehozhatunk erős és egyedi jelszavakat a gombra kattintva.", + "description": "Two part message", + "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." + }, + "generatorNudgeBodyTwo": { + "message": "a bejelentkezések biztonságának megőrzéséhez.", + "description": "Two part message", + "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." + }, + "generatorNudgeBodyAria": { + "message": "Könnyedén hozhatunk létre erős és egyedi jelszavakat a Jelszó generálása gombra kattintva, amely segít megőrizni a bejelentkezések biztonságát.", + "description": "Aria label for the body content of the generator nudge" + }, + "noPermissionsViewPage": { + "message": "Nincs jogosultság az oldal megtekintéséhez. Próbáljunk meg másik fiókkal bejelentkezni." } } diff --git a/apps/browser/src/_locales/id/messages.json b/apps/browser/src/_locales/id/messages.json index 101fcf43795..af1ccf80034 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": "Logo Bitwarden" + }, "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": "Ikuti langkah-langkah berikut untuk menyelesaikan kegiatan masuk dengan kunci keamanan Anda." + }, "restartRegistration": { "message": "Mulai ulang pendaftaran" }, @@ -1056,23 +1062,55 @@ "notificationAddSave": { "message": "Iya, Simpan Sekarang" }, - "loginSaveSuccessDetails": { - "message": "$USERNAME$ disimpan ke Bitwarden.", + "notificationViewAria": { + "message": "Lihat $ITEMNAME$, buka di jendela baru", "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.", + "notificationNewItemAria": { + "message": "New Item, opens in new window", + "description": "Aria label for the new item button in notification bar confirmation message when error is prompted" + }, + "notificationEditTooltip": { + "message": "Sunting sebelum menyimpan", + "description": "Tooltip and Aria label for edit button on cipher item" + }, + "newNotification": { + "message": "Pemberitahuan baru" + }, + "labelWithNotification": { + "message": "$LABEL$: Pemberitahuan baru", + "description": "Label for the notification with a new login suggestion.", "placeholders": { - "username": { - "content": "$1" + "label": { + "content": "$1", + "example": "Login" } - }, - "description": "Shown to user after login is updated." + } + }, + "notificationLoginSaveConfirmation": { + "message": "saved to Bitwarden.", + "description": "Shown to user after item is saved." + }, + "notificationLoginUpdatedConfirmation": { + "message": "updated in Bitwarden.", + "description": "Shown to user after item is updated." + }, + "selectItemAriaLabel": { + "message": "Select $ITEMTYPE$, $ITEMNAME$", + "description": "Used by screen readers. $1 is the item type (like vault or folder), $2 is the selected item name.", + "placeholders": { + "itemType": { + "content": "$1" + }, + "itemName": { + "content": "$2" + } + } }, "saveAsNewLoginAction": { "message": "Simpan sebagai log masuk baru", @@ -1082,12 +1120,16 @@ "message": "Perbarui log masuk", "description": "Button text for updating an existing login entry." }, - "saveLoginPrompt": { - "message": "Simpan log masuk?", + "unlockToSave": { + "message": "Unlock to save this login", + "description": "User prompt to take action in order to save the login they just entered." + }, + "saveLogin": { + "message": "Simpan log masuk", "description": "Prompt asking the user if they want to save their login details." }, - "updateLoginPrompt": { - "message": "Perbarui log masuk yang ada?", + "updateLogin": { + "message": "Perbarui log masuk yang sudah ada", "description": "Prompt asking the user if they want to update an existing login entry." }, "loginSaveSuccess": { @@ -1099,7 +1141,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": "Bagus! Anda telah mengambil langkah-langkah untuk membuat Anda dan $ORGANIZATION$ lebih aman.", "placeholders": { "organization": { "content": "$1" @@ -1108,7 +1150,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": "Terima kasih telah membuat $ORGANIZATION$ menjadi lebih aman. Anda memiliki $TASK_COUNT$ kata sandi lagi untuk diperbarui.", "placeholders": { "organization": { "content": "$1" @@ -1120,7 +1162,7 @@ "description": "Shown to user after login is updated." }, "nextSecurityTaskAction": { - "message": "Change next password", + "message": "Ubah kata sandi selanjutnya", "description": "Message prompting user to undertake completion of another security task." }, "saveFailure": { @@ -1323,8 +1365,8 @@ "featureUnavailable": { "message": "Fitur Tidak Tersedia" }, - "encryptionKeyMigrationRequired": { - "message": "Kunci enkripsi migrasi dibutuhkan. Silakan masuk melalui brankas web untuk memperbarui kunci enkripsi Anda." + "legacyEncryptionUnsupported": { + "message": "Legacy encryption is no longer supported. Please contact support to recover your account." }, "premiumMembership": { "message": "Keanggotaan Premium" @@ -1557,6 +1599,24 @@ "autofillSuggestionsSectionTitle": { "message": "Saran isi otomatis" }, + "autofillSpotlightTitle": { + "message": "Easily find autofill suggestions" + }, + "autofillSpotlightDesc": { + "message": "Turn off your browser's autofill settings, so they don't conflict with Bitwarden." + }, + "turnOffBrowserAutofill": { + "message": "Turn off $BROWSER$ autofill", + "placeholders": { + "browser": { + "content": "$1", + "example": "Chrome" + } + } + }, + "turnOffAutofill": { + "message": "Turn off autofill" + }, "showInlineMenuLabel": { "message": "Tampilkan saran isi otomatis pada kolom formulir" }, @@ -2144,6 +2204,9 @@ "useThisPassword": { "message": "Gunakan kata sandi ini" }, + "useThisPassphrase": { + "message": "Use this passphrase" + }, "useThisUsername": { "message": "Gunakan nama pengguna ini" }, @@ -2167,15 +2230,6 @@ "vaultTimeoutAction1": { "message": "Batas waktu tindakan" }, - "newCustomizationOptionsCalloutTitle": { - "message": "Opsi penyesuaian baru" - }, - "newCustomizationOptionsCalloutContent": { - "message": "Sesuaikan pengalaman brankas Anda dengan aksi salin cepat, mode kompak, dan lainnya!" - }, - "newCustomizationOptionsCalloutLink": { - "message": "Lihat semua pengaturan Penampilan" - }, "lock": { "message": "Kunci", "description": "Verb form: to make secure or inaccessible by" @@ -2323,7 +2377,7 @@ "message": "Kebijakan Privasi" }, "yourNewPasswordCannotBeTheSameAsYourCurrentPassword": { - "message": "Your new password cannot be the same as your current password." + "message": "Kata sandi baru Anda tidak boleh sama dengan kata sandi Anda yang sekarang." }, "hintEqualsPassword": { "message": "Petunjuk kata sandi Anda tidak boleh sama dengan kata sandi Anda." @@ -2520,10 +2574,10 @@ "message": "Ubah lebih cepat kata sandi yang berrisiko" }, "changeAtRiskPasswordsFasterDesc": { - "message": "Update your settings so you can quickly autofill your passwords and generate new ones" + "message": "Perbarui pengaturan Anda sehingga Anda dapat dengan cepat mengisi otomatis kata sandi Anda dan menghasilkan kata sandi baru" }, "reviewAtRiskLogins": { - "message": "Review at-risk logins" + "message": "Tinjau ulang info masuk yang berpotensi bahaya" }, "reviewAtRiskPasswords": { "message": "Tinjau kata sandi yang berrisiko" @@ -2533,30 +2587,30 @@ "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": "Gambaran daftar info masuk yang berpotensi bahaya." }, "generatePasswordSlideDesc": { - "message": "Quickly generate a strong, unique password with the Bitwarden autofill menu on the at-risk site.", + "message": "Hasilkan kata sandi yang kuat dan unik dengan cepat dengan menu isi otomatis Bitwarden pada situs yang berpotensi bahaya.", "description": "Description of the generate password slide on the at-risk password page carousel" }, "generatePasswordSlideImgAltPeriod": { - "message": "Illustration of the Bitwarden autofill menu displaying a generated password." + "message": "Gambaran dari menu isi otomatis Bitwarden yang menampilkan kata sandi yang dihasilkan." }, "updateInBitwarden": { - "message": "Update in Bitwarden" + "message": "Perbarui di Bitwarden" }, "updateInBitwardenSlideDesc": { - "message": "Bitwarden will then prompt you to update the password in the password manager.", + "message": "Bitwarden akan meminta Anda untuk memperbarui kata sandi di pengelola sandi.", "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": "Gambaran dari pemberitahuan Bitwarden yang meminta pengguna untuk memperbarui info masuk." }, "turnOnAutofill": { - "message": "Turn on autofill" + "message": "Nyalakan isi otomatis" }, "turnedOnAutofill": { - "message": "Turned on autofill" + "message": "Telah menyalakan isi otomatis" }, "dismiss": { "message": "Tutup" @@ -2623,6 +2677,10 @@ "message": "Semua Sends", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, + "maxAccessCountReached": { + "message": "Max access count reached", + "description": "This text will be displayed after a Send has been accessed the maximum amount of times." + }, "hideTextByDefault": { "message": "Sembunyikan teks secara bawaan" }, @@ -2963,14 +3021,14 @@ "copyCustomFieldNameNotUnique": { "message": "Tidak ada pengidentifikasi unik yang ditemukan." }, - "convertOrganizationEncryptionDesc": { - "message": "$ORGANIZATION$ menggunakan SSO dengan server kunci yang dihosting sendiri. Kata sandi utama tidak lagi diperlukan untuk masuk untuk anggota organisasi ini.", - "placeholders": { - "organization": { - "content": "$1", - "example": "My Org Name" - } - } + "removeMasterPasswordForOrganizationUserKeyConnector": { + "message": "A master password is no longer required for members of the following organization. Please confirm the domain below with your organization administrator." + }, + "organizationName": { + "message": "Organization name" + }, + "keyConnectorDomain": { + "message": "Key Connector domain" }, "leaveOrganization": { "message": "Tinggalkan Organisasi" @@ -3006,7 +3064,7 @@ } }, "exportingIndividualVaultWithAttachmentsDescription": { - "message": "Only the individual vault items including attachments associated with $EMAIL$ will be exported. Organization vault items will not be included", + "message": "Hanya benda-benda brankas satuan termasuk lampiran yang terhubung dengan $EMAIL$ yang akan diekspor. Benda brankas organisasi tidak akan disertakan", "placeholders": { "email": { "content": "$1", @@ -3036,7 +3094,7 @@ "message": "Bitwarden tidak bisa mendekripsi butir brankas yang tercantum di bawah." }, "contactCSToAvoidDataLossPart1": { - "message": "Contact customer success", + "message": "Hubungi layanan pelanggan", "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" }, "contactCSToAvoidDataLossPart2": { @@ -3532,12 +3590,36 @@ "deviceTrusted": { "message": "Perangkat dipercaya" }, - "sendsNoItemsTitle": { - "message": "Tidak ada Send yang aktif", + "trustOrganization": { + "message": "Percayai organisasi" + }, + "trust": { + "message": "Percayai" + }, + "doNotTrust": { + "message": "Jangan percayai" + }, + "organizationNotTrusted": { + "message": "Organisasi tidak tepercaya" + }, + "emergencyAccessTrustWarning": { + "message": "Demi keamanan akun Anda, hanya konfirmasi jika Anda telah memberikan akses darurat ke pengguna ini dan sidik jari mereka cocok dengan apa yang ditampilkan pada akun mereka" + }, + "orgTrustWarning": { + "message": "Demi keamanan akun Anda, hanya lanjutkan apabila Anda adalah anggota dari organisasi ini, pemulihan akun telah aktif, dan sidik jari yang ditampilkan berikut cocok dengan sidik jari organisasi." + }, + "orgTrustWarning1": { + "message": "Organisasi ini memiliki kebijakan perusahaan yang akan mendaftarkan Anda pada pemulihan akun. Pendaftaran ini akan membolehkan pengelola organisasi untuk mengubah kata sandi Anda. Hanya lanjutkan jika Anda mengenali organisasi ini dan frasa sidik jari yang ditampilkan berikut cocok dengan sidik jari organisasi." + }, + "trustUser": { + "message": "Percayai pengguna" + }, + "sendsTitleNoItems": { + "message": "Send sensitive information safely", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, - "sendsNoItemsMessage": { - "message": "Gunakan Send untuk membagikan informasi terenkripsi secara aman dengan siapapun.", + "sendsBodyNoItems": { + "message": "Share files and data securely with anyone, on any platform. Your information will remain end-to-end encrypted while limiting exposure.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "inputRequired": { @@ -4272,7 +4354,7 @@ } }, "viewItemTitleWithField": { - "message": "View item - $ITEMNAME$ - $FIELD$", + "message": "Lihat benda - $ITEMNAME$ - $FIELD$", "description": "Title for a link that opens a view for an item.", "placeholders": { "itemname": { @@ -4296,7 +4378,7 @@ } }, "autofillTitleWithField": { - "message": "Autofill - $ITEMNAME$ - $FIELD$", + "message": "Isi otomatis - $ITEMNAME$ - $FIELD$", "description": "Title for a button that autofills a login item.", "placeholders": { "itemname": { @@ -4456,6 +4538,33 @@ } } }, + "downloadBitwarden": { + "message": "Unduh Bitwarden" + }, + "downloadBitwardenOnAllDevices": { + "message": "Unduh Bitwarden" + }, + "getTheMobileApp": { + "message": "Dapatkan aplikasi ponsel" + }, + "getTheMobileAppDesc": { + "message": "Akses kata sandi Anda di perjalanan dengan aplikasi ponsel Bitwarden." + }, + "getTheDesktopApp": { + "message": "Dapatkan aplikasi desktop" + }, + "getTheDesktopAppDesc": { + "message": "Akses brankas Anda tanpa sebuah peramban, kemudian atur buka dengan biometrik untuk mempercepat membuka di aplikasi desktop dan ekstensi peramban." + }, + "downloadFromBitwardenNow": { + "message": "Unduh dari bitwarden.com sekarang" + }, + "getItOnGooglePlay": { + "message": "Get it on Google Play" + }, + "downloadOnTheAppStore": { + "message": "Download on the App Store" + }, "permanentlyDeleteAttachmentConfirmation": { "message": "Apakah Anda yakin ingin menghapus lampiran ini selamanya?" }, @@ -4700,10 +4809,10 @@ } }, "reorderWebsiteUriButton": { - "message": "Reorder website URI. Use arrow key to move item up or down." + "message": "Urutkan URI situs web. Gunakan tombol panah untuk memindahkan benda ke atas atau ke bawah." }, "reorderFieldUp": { - "message": "$LABEL$ moved up, position $INDEX$ of $LENGTH$", + "message": "$LABEL$ dipindah ke atas, terletak di $INDEX$ dari $LENGTH$", "placeholders": { "label": { "content": "$1", @@ -4720,13 +4829,13 @@ } }, "selectCollectionsToAssign": { - "message": "Select collections to assign" + "message": "Pilih koleksi untuk ditetapkan" }, "personalItemTransferWarningSingular": { - "message": "1 item will be permanently transferred to the selected organization. You will no longer own this item." + "message": "1 benda akan dipindahkan selamanya ke organisasi terpilih. Anda tidak akan lagi memiliki benda ini." }, "personalItemsTransferWarningPlural": { - "message": "$PERSONAL_ITEMS_COUNT$ items will be permanently transferred to the selected organization. You will no longer own these items.", + "message": "$PERSONAL_ITEMS_COUNT$ benda akan dipindahkan selamanya ke organisasi terpilih. Anda tidak akan lagi memiliki benda-benda tersebut.", "placeholders": { "personal_items_count": { "content": "$1", @@ -4735,7 +4844,7 @@ } }, "personalItemWithOrgTransferWarningSingular": { - "message": "1 item will be permanently transferred to $ORG$. You will no longer own this item.", + "message": "1 benda akan dipindahkan selamanya ke $ORG$. Anda tidak akan lagi memiliki benda ini.", "placeholders": { "org": { "content": "$1", @@ -4744,7 +4853,7 @@ } }, "personalItemsWithOrgTransferWarningPlural": { - "message": "$PERSONAL_ITEMS_COUNT$ items will be permanently transferred to $ORG$. You will no longer own these items.", + "message": "$PERSONAL_ITEMS_COUNT$ benda akan dipindahkan selamanya ke $ORG$. Anda tidak akan lagi memiliki benda-benda tersebut.", "placeholders": { "personal_items_count": { "content": "$1", @@ -4917,6 +5026,15 @@ "biometricsStatusHelptextUnavailableReasonUnknown": { "message": "Biometric unlock is currently unavailable for an unknown reason." }, + "unlockVault": { + "message": "Unlock your vault in seconds" + }, + "unlockVaultDesc": { + "message": "You can customize your unlock and timeout settings to more quickly access your vault." + }, + "unlockPinSet": { + "message": "Unlock PIN set" + }, "authenticating": { "message": "Authenticating" }, @@ -4928,8 +5046,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": { @@ -5079,42 +5197,6 @@ "beta": { "message": "Beta" }, - "importantNotice": { - "message": "Pemberitahuan penting" - }, - "setupTwoStepLogin": { - "message": "Siapkan log masuk dua langkah" - }, - "newDeviceVerificationNoticeContentPage1": { - "message": "Bitwarden akan mengirim suatu kode ke akun surel Anda untuk memverifikasi log masuk dari perangkat baru sejak Februari 2025." - }, - "newDeviceVerificationNoticeContentPage2": { - "message": "Anda dapat menyiapkan log masuk dua langkah sebagai cara alternatif untuk melindungi akun Anda atau mengubah surel Anda ke yang bisa Anda akses." - }, - "remindMeLater": { - "message": "Ingatkan saya nanti" - }, - "newDeviceVerificationNoticePageOneFormContent": { - "message": "Apakah Anda punya akses yang handal ke surel Anda, $EMAIL$?", - "placeholders": { - "email": { - "content": "$1", - "example": "your_name@email.com" - } - } - }, - "newDeviceVerificationNoticePageOneEmailAccessNo": { - "message": "Tidak" - }, - "newDeviceVerificationNoticePageOneEmailAccessYes": { - "message": "Ya, saya dapat mengakses surel saya secara handla" - }, - "turnOnTwoStepLogin": { - "message": "Nyalakan log masuk dua langkah" - }, - "changeAcctEmail": { - "message": "Ubah surel akun" - }, "extensionWidth": { "message": "Lebar ekstensi" }, @@ -5169,6 +5251,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 +5283,98 @@ }, "secureDevicesBody": { "message": "Save unlimited passwords across unlimited devices with Bitwarden mobile, browser, and desktop apps." + }, + "nudgeBadgeAria": { + "message": "1 notification" + }, + "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!" + }, + "hasItemsVaultNudgeBodyOne": { + "message": "Autofill items for the current page" + }, + "hasItemsVaultNudgeBodyTwo": { + "message": "Favorite items for easy access" + }, + "hasItemsVaultNudgeBodyThree": { + "message": "Search your vault for something else" + }, + "newLoginNudgeTitle": { + "message": "Save time with autofill" + }, + "newLoginNudgeBodyOne": { + "message": "Include a", + "description": "This is in multiple parts to allow for bold text in the middle of the sentence.", + "example": "Include a Website so this login appears as an autofill suggestion." + }, + "newLoginNudgeBodyBold": { + "message": "Situs web", + "description": "This is in multiple parts to allow for bold text in the middle of the sentence.", + "example": "Include a Website so this login appears as an autofill suggestion." + }, + "newLoginNudgeBodyTwo": { + "message": "agar info masuk ini muncul sebagai saran pengisian otomatis.", + "description": "This is in multiple parts to allow for bold text in the middle of the sentence.", + "example": "Include a Website so this login appears as an autofill suggestion." + }, + "newCardNudgeTitle": { + "message": "Pembayaran daring yang lancar" + }, + "newCardNudgeBody": { + "message": "Dengan kartu, isi otomatis dengan mudah formulir pembayaran secara aman dan akurat." + }, + "newIdentityNudgeTitle": { + "message": "Sederhanakan pembuatan akun" + }, + "newIdentityNudgeBody": { + "message": "Dengan identitas, isi otomatis dengan cepat formulir pendaftaran atau kontrak yang panjang." + }, + "newNoteNudgeTitle": { + "message": "Menjaga data sensitif Anda tetap aman" + }, + "newNoteNudgeBody": { + "message": "Dengan catatan, simpan secara aman data sensitif seperti rincian perbankan atau asuransi." + }, + "newSshNudgeTitle": { + "message": "Akses SSH yang ramah pengembang" + }, + "newSshNudgeBodyOne": { + "message": "Simpan kunci-kunci Anda dan sambungkan dengan agen SSH untuk otentikasi yang cepat dan terenkripsi.", + "description": "Two part message", + "example": "Store your keys and connect with the SSH agent for fast, encrypted authentication. Learn more about SSH agent" + }, + "newSshNudgeBodyTwo": { + "message": "Pelajari lebih lanjut tentang agen SSH", + "description": "Two part message", + "example": "Store your keys and connect with the SSH agent for fast, encrypted authentication. Learn more about SSH agent" + }, + "generatorNudgeTitle": { + "message": "Quickly create passwords" + }, + "generatorNudgeBodyOne": { + "message": "Easily create strong and unique passwords by clicking on", + "description": "Two part message", + "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." + }, + "generatorNudgeBodyTwo": { + "message": "to help you keep your logins secure.", + "description": "Two part message", + "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." + }, + "generatorNudgeBodyAria": { + "message": "Easily create strong and unique passwords by clicking on the Generate password button to help you keep your logins secure.", + "description": "Aria label for the body content of the generator nudge" + }, + "noPermissionsViewPage": { + "message": "You do not have permissions to view this page. Try logging in with a different account." } } diff --git a/apps/browser/src/_locales/it/messages.json b/apps/browser/src/_locales/it/messages.json index 5f9a6ca7dca..81282951d93 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": "Logo di Bitwarden" + }, "extName": { "message": "Bitwarden Password Manager", "description": "Extension name, MUST be less than 40 characters (Safari restriction)" @@ -20,7 +23,7 @@ "message": "Crea account" }, "newToBitwarden": { - "message": "Sei un nuovo utente?" + "message": "Sei nuovo su Bitwarden?" }, "logInWithPasskey": { "message": "Accedi con passkey" @@ -29,7 +32,7 @@ "message": "Usa il Single Sign-On" }, "welcomeBack": { - "message": "Bentornat*" + "message": "Bentornato/a" }, "setAStrongPassword": { "message": "Imposta una password robusta" @@ -129,7 +132,7 @@ "message": "Copia password" }, "copyPassphrase": { - "message": "Copia passphrase" + "message": "Copia frase segreta" }, "copyNote": { "message": "Copia nota" @@ -459,13 +462,13 @@ "message": "Genera password" }, "generatePassphrase": { - "message": "Genera passphrase" + "message": "Genera frase segreta" }, "passwordGenerated": { "message": "Parola d'accesso generata" }, "passphraseGenerated": { - "message": "Frase d'accesso generata" + "message": "Frase segreta generata" }, "usernameGenerated": { "message": "Nome utente generato" @@ -883,6 +886,9 @@ "followTheStepsBelowToFinishLoggingIn": { "message": "Segui i passaggi qui sotto per completare l'accesso." }, + "followTheStepsBelowToFinishLoggingInWithSecurityKey": { + "message": "Segui i passaggi seguenti per finire di accedere con la tua chiave di sicurezza." + }, "restartRegistration": { "message": "Ricomincia la registrazione" }, @@ -1056,50 +1062,86 @@ "notificationAddSave": { "message": "Salva" }, - "loginSaveSuccessDetails": { - "message": "$USERNAME$ salvato in Bitwarden.", + "notificationViewAria": { + "message": "Visualizza $ITEMNAME$, si apre in una nuova finestra", "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.", + "notificationNewItemAria": { + "message": "Nuovo elemento, si apre in una nuova finestra", + "description": "Aria label for the new item button in notification bar confirmation message when error is prompted" + }, + "notificationEditTooltip": { + "message": "Modifica prima di salvare", + "description": "Tooltip and Aria label for edit button on cipher item" + }, + "newNotification": { + "message": "Nuova notifica" + }, + "labelWithNotification": { + "message": "$LABEL$: Nuova notifica", + "description": "Label for the notification with a new login suggestion.", "placeholders": { - "username": { - "content": "$1" + "label": { + "content": "$1", + "example": "Login" } - }, - "description": "Shown to user after login is updated." + } + }, + "notificationLoginSaveConfirmation": { + "message": "salvato in Bitwarden.", + "description": "Shown to user after item is saved." + }, + "notificationLoginUpdatedConfirmation": { + "message": "aggiornato in Bitwarden.", + "description": "Shown to user after item is updated." + }, + "selectItemAriaLabel": { + "message": "Seleziona $ITEMTYPE$, $ITEMNAME$", + "description": "Used by screen readers. $1 is the item type (like vault or folder), $2 is the selected item name.", + "placeholders": { + "itemType": { + "content": "$1" + }, + "itemName": { + "content": "$2" + } + } }, "saveAsNewLoginAction": { - "message": "Salva come nuovo accesso", + "message": "Salva come nuovo login", "description": "Button text for saving login details as a new entry." }, "updateLoginAction": { - "message": "Aggiorna accesso", + "message": "Aggiorna login", "description": "Button text for updating an existing login entry." }, - "saveLoginPrompt": { - "message": "Salvare l'accesso?", + "unlockToSave": { + "message": "Sblocca per salvare questo login", + "description": "User prompt to take action in order to save the login they just entered." + }, + "saveLogin": { + "message": "Salva il login", "description": "Prompt asking the user if they want to save their login details." }, - "updateLoginPrompt": { - "message": "Aggiornare l'accesso esistente?", + "updateLogin": { + "message": "Aggiorna login esistente", "description": "Prompt asking the user if they want to update an existing login entry." }, "loginSaveSuccess": { - "message": "Accesso salvato", + "message": "Login salvato", "description": "Message displayed when login details are successfully saved." }, "loginUpdateSuccess": { - "message": "Accesso aggiornato", + "message": "Login aggiornato", "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": "Congratulazioni! Hai reso $ORGANIZATION$ e te stesso più sicuri.", "placeholders": { "organization": { "content": "$1" @@ -1108,7 +1150,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": "Grazie per aver reso $ORGANIZATION$ più sicuro. Hai altre $TASK_COUNT$ password da aggiornare.", "placeholders": { "organization": { "content": "$1" @@ -1120,7 +1162,7 @@ "description": "Shown to user after login is updated." }, "nextSecurityTaskAction": { - "message": "Change next password", + "message": "Cambia la prossima password", "description": "Message prompting user to undertake completion of another security task." }, "saveFailure": { @@ -1128,7 +1170,7 @@ "description": "Error message shown when the system fails to save login details." }, "saveFailureDetails": { - "message": "Oh no! Non abbiamo potuto salvarlo. Prova a inserire manualmente i dettagli.", + "message": "Oh no! Il salvataggio non è riuscito. Prova a inserire i dati manualmente.", "description": "Detailed error message shown when saving login details fails." }, "enableChangedPasswordNotification": { @@ -1323,8 +1365,8 @@ "featureUnavailable": { "message": "Funzionalità non disponibile" }, - "encryptionKeyMigrationRequired": { - "message": "Migrazione della chiave di crittografia obbligatoria. Accedi tramite la cassaforte web per aggiornare la tua chiave di crittografia." + "legacyEncryptionUnsupported": { + "message": "La crittografia legacy non è più supportata. Contatta l'assistenza per recuperare il tuo account." }, "premiumMembership": { "message": "Abbonamento Premium" @@ -1557,6 +1599,24 @@ "autofillSuggestionsSectionTitle": { "message": "Suggerimenti per il riempimento automatico" }, + "autofillSpotlightTitle": { + "message": "Trova facilmente suggerimenti di riempimento automatico" + }, + "autofillSpotlightDesc": { + "message": "Disattiva le impostazioni di riempimento automatico del tuo browser, in modo da non entrare in conflitto con Bitwarden." + }, + "turnOffBrowserAutofill": { + "message": "Disattiva il riempimento automatico di $BROWSER$", + "placeholders": { + "browser": { + "content": "$1", + "example": "Chrome" + } + } + }, + "turnOffAutofill": { + "message": "Disattiva il riempimento automatico" + }, "showInlineMenuLabel": { "message": "Mostra suggerimenti di riempimento automatico nei campi del modulo" }, @@ -2144,6 +2204,9 @@ "useThisPassword": { "message": "Usa questa password" }, + "useThisPassphrase": { + "message": "Usa questa frase segreta" + }, "useThisUsername": { "message": "Usa questo nome utente" }, @@ -2167,15 +2230,6 @@ "vaultTimeoutAction1": { "message": "Azione al timeout" }, - "newCustomizationOptionsCalloutTitle": { - "message": "Nuove opzioni di personalizzazione" - }, - "newCustomizationOptionsCalloutContent": { - "message": "Personalizza l'esperienza della tua cassaforte con azioni di copia rapida, modalità compatta e altro ancora!" - }, - "newCustomizationOptionsCalloutLink": { - "message": "Vedi tutte le impostazioni di aspetto" - }, "lock": { "message": "Blocca", "description": "Verb form: to make secure or inaccessible by" @@ -2323,7 +2377,7 @@ "message": "Informativa sulla Privacy" }, "yourNewPasswordCannotBeTheSameAsYourCurrentPassword": { - "message": "Your new password cannot be the same as your current password." + "message": "La tua nuova password non può essere la stessa della tua password attuale." }, "hintEqualsPassword": { "message": "Il suggerimento della password non può essere uguale alla password." @@ -2529,18 +2583,18 @@ "message": "Rivedi parole d'accesso a rischio" }, "reviewAtRiskLoginsSlideDesc": { - "message": "Le parole d'accesso dell'organizzazione sono a rischio perché sono deboli, riutilizzate, e/o esposte.", + "message": "Le password dell'organizzazione sono a rischio perché sono deboli, riutilizzate, e/o esposte.", "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": "Illustrazione di una lista di login a rischio." }, "generatePasswordSlideDesc": { "message": "Genera rapidamente una parola d'accesso forte e unica con il menu' di riempimento automatico Bitwarden nel sito a rischio.", "description": "Description of the generate password slide on the at-risk password page carousel" }, "generatePasswordSlideImgAltPeriod": { - "message": "Illustration of the Bitwarden autofill menu displaying a generated password." + "message": "Illustrazione del menu di riempimento automatico Bitwarden che mostra una password generata." }, "updateInBitwarden": { "message": "Aggiorna in Bitwarden" @@ -2550,7 +2604,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": "Illustrazione di una notifica Bitwarden che richiede all'utente di aggiornare il login." }, "turnOnAutofill": { "message": "Attiva riempimento automatico" @@ -2623,6 +2677,10 @@ "message": "Tutti i Send", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, + "maxAccessCountReached": { + "message": "Raggiunto il limite massimo degli accessi", + "description": "This text will be displayed after a Send has been accessed the maximum amount of times." + }, "hideTextByDefault": { "message": "Nascondi testo come default" }, @@ -2963,14 +3021,14 @@ "copyCustomFieldNameNotUnique": { "message": "Nessun identificatore univoco trovato." }, - "convertOrganizationEncryptionDesc": { - "message": "$ORGANIZATION$ sta usando SSO con un server self-hosted. Una password principale non è più necessaria per accedere per i membri di questa organizzazione.", - "placeholders": { - "organization": { - "content": "$1", - "example": "My Org Name" - } - } + "removeMasterPasswordForOrganizationUserKeyConnector": { + "message": "La password principale non è più richiesta per i membri dell'organizzazione. Per favore, conferma il dominio qui sotto con l'amministratore." + }, + "organizationName": { + "message": "Nome organizzazione" + }, + "keyConnectorDomain": { + "message": "Dominio Key Connector" }, "leaveOrganization": { "message": "Lascia organizzazione" @@ -3006,7 +3064,7 @@ } }, "exportingIndividualVaultWithAttachmentsDescription": { - "message": "Only the individual vault items including attachments associated with $EMAIL$ will be exported. Organization vault items will not be included", + "message": "Solo gli elementi della cassaforte personale associati a $EMAIL$, includendo gli allegati, saranno esportati. Gli elementi della cassaforte dell'organizzazione non saranno inclusi", "placeholders": { "email": { "content": "$1", @@ -3074,7 +3132,7 @@ } }, "passphraseNumWordsRecommendationHint": { - "message": " Usa $RECOMMENDED$ parole o più per generare una passphrase forte.", + "message": " Usa $RECOMMENDED$ parole o più per generare una frase segreta forte.", "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": { @@ -3532,12 +3590,36 @@ "deviceTrusted": { "message": "Dispositivo fidato" }, - "sendsNoItemsTitle": { - "message": "Nessun Send attivo", + "trustOrganization": { + "message": "Fidati dell'organizzazione" + }, + "trust": { + "message": "Fidati" + }, + "doNotTrust": { + "message": "Non fidarti" + }, + "organizationNotTrusted": { + "message": "L'organizzazione non è fidata" + }, + "emergencyAccessTrustWarning": { + "message": "Per la sicurezza del tuo account, conferma solo se hai concesso l'accesso di emergenza a questo utente e le loro impronte digitali corrispondono a quelle contenute nel loro account" + }, + "orgTrustWarning": { + "message": "Per la sicurezza del tuo account, procedi solo se sei un membro di questa organizzazione, il recupero dell'account è abilitato e l'impronta digitale visualizzata di seguito corrisponde all'impronta digitale dell'organizzazione." + }, + "orgTrustWarning1": { + "message": "Questa organizzazione ha una politica Enterprise che ti iscriverà al recupero dell'account. La registrazione consentirà agli amministratori dell'organizzazione di modificare la password. Procedi solo se riconosci questa organizzazione e la frase di impronta digitale mostrata di seguito corrisponde all'impronta digitale dell'organizzazione." + }, + "trustUser": { + "message": "Fidati dell'utente" + }, + "sendsTitleNoItems": { + "message": "Invia informazioni sensibili in modo sicuro", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, - "sendsNoItemsMessage": { - "message": "Utilizza un Send per condividere in modo sicuro le informazioni con qualsiasi utente.", + "sendsBodyNoItems": { + "message": "Condividi file e dati in modo sicuro con chiunque, su qualsiasi piattaforma. Le tue informazioni saranno crittografate end-to-end.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "inputRequired": { @@ -3738,7 +3820,7 @@ "description": "Button text to display within inline menu when there are no matching items on a login field" }, "addNewLoginItemAria": { - "message": "Aggiungi un nuovo elemento \"login\" alla cassaforte, apri in una nuova finestra", + "message": "Aggiungi un nuovo elemento 'login' alla cassaforte (si apre in una nuova finestra)", "description": "Screen reader text (aria-label) for new login button within inline menu" }, "newCard": { @@ -4272,7 +4354,7 @@ } }, "viewItemTitleWithField": { - "message": "View item - $ITEMNAME$ - $FIELD$", + "message": "Visualizza elemento - $ITEMNAME$ - $FIELD$", "description": "Title for a link that opens a view for an item.", "placeholders": { "itemname": { @@ -4296,7 +4378,7 @@ } }, "autofillTitleWithField": { - "message": "Autofill - $ITEMNAME$ - $FIELD$", + "message": "Riempimento automatico - $ITEMNAME$ - $FIELD$", "description": "Title for a button that autofills a login item.", "placeholders": { "itemname": { @@ -4456,6 +4538,33 @@ } } }, + "downloadBitwarden": { + "message": "Scarica Bitwarden" + }, + "downloadBitwardenOnAllDevices": { + "message": "Scarica Bitwarden su tutti i dispositivi" + }, + "getTheMobileApp": { + "message": "Scarica l'app mobile" + }, + "getTheMobileAppDesc": { + "message": "Accedi alle tue password ovunque con l'app Bitwarden per dispositivi mobili." + }, + "getTheDesktopApp": { + "message": "Scarica l'app desktop" + }, + "getTheDesktopAppDesc": { + "message": "Accedi alla tua cassaforte senza browser, quindi imposta lo sblocco biometrico per accelerare l'accesso sia all'app desktop che all'estensione." + }, + "downloadFromBitwardenNow": { + "message": "Scarica ora da bitwarden.com" + }, + "getItOnGooglePlay": { + "message": "Disponible su Google Play" + }, + "downloadOnTheAppStore": { + "message": "Scarica dall'App Store" + }, "permanentlyDeleteAttachmentConfirmation": { "message": "Sei sicuro di voler eliminare definitivamente questo allegato?" }, @@ -4917,6 +5026,15 @@ "biometricsStatusHelptextUnavailableReasonUnknown": { "message": "Lo sblocco biometrico non è attualmente disponibile per un motivo sconosciuto." }, + "unlockVault": { + "message": "Sblocca la cassaforte in secondi" + }, + "unlockVaultDesc": { + "message": "Puoi personalizzare le impostazioni di sblocco e timeout per accedere più rapidamente alla tua cassaforte." + }, + "unlockPinSet": { + "message": "Sblocca PIN impostato" + }, "authenticating": { "message": "Autenticazione" }, @@ -4928,8 +5046,8 @@ "message": "Password rigenerata", "description": "Notification message for when a password has been regenerated" }, - "saveLoginToBitwarden": { - "message": "Salvare il login su Bitwarden?", + "saveToBitwarden": { + "message": "Salva su Bitwarden", "description": "Confirmation message for saving a login to Bitwarden" }, "spaceCharacterDescriptor": { @@ -5079,42 +5197,6 @@ "beta": { "message": "Beta" }, - "importantNotice": { - "message": "Avviso importante" - }, - "setupTwoStepLogin": { - "message": "Imposta accesso in due passaggi" - }, - "newDeviceVerificationNoticeContentPage1": { - "message": "Bitwarden invierà un codice all'email del tuo account per verificare gli accessi da nuovi dispositivi a partire da febbraio 2025." - }, - "newDeviceVerificationNoticeContentPage2": { - "message": "Puoi impostare l'accesso in due passaggi come modo alternativo per proteggere il tuo account, o cambiare la tua e-mail in una alla quale puoi accedere." - }, - "remindMeLater": { - "message": "Ricordamelo più tardi" - }, - "newDeviceVerificationNoticePageOneFormContent": { - "message": "Riesci ancora ad accedere a questa email, $EMAIL$?", - "placeholders": { - "email": { - "content": "$1", - "example": "your_name@email.com" - } - } - }, - "newDeviceVerificationNoticePageOneEmailAccessNo": { - "message": "No, non riesco" - }, - "newDeviceVerificationNoticePageOneEmailAccessYes": { - "message": "Sì, riesco ad accedere a questa email" - }, - "turnOnTwoStepLogin": { - "message": "Attiva accesso in due passaggi" - }, - "changeAcctEmail": { - "message": "Cambia l'email dell'account" - }, "extensionWidth": { "message": "Larghezza estensione" }, @@ -5169,31 +5251,130 @@ "changeAtRiskPassword": { "message": "Cambia parola d'accesso a rischio" }, + "settingsVaultOptions": { + "message": "Opzioni cassaforte" + }, + "emptyVaultDescription": { + "message": "La cassaforte protegge e tiene al sicuro non solo le password, ma anche le passkey, i nomi utente, le identità, le carte e le note." + }, "introCarouselLabel": { - "message": "Welcome to Bitwarden" + "message": "Benvenuto su Bitwarden" }, "securityPrioritized": { - "message": "Security, prioritized" + "message": "Sicurezza alla massima priorità" }, "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": "Salva login, carte e identità nella tua cassaforte sicura. Bitwarden usa la crittografia end-to-end e zero-knowledge per proteggere i tuoi dati." }, "quickLogin": { - "message": "Quick and easy login" + "message": "Autenticazione facile e veloce" }, "quickLoginBody": { - "message": "Set up biometric unlock and autofill to log into your accounts without typing a single letter." + "message": "Imposta lo sblocco biometrico e il riempimento automatico per accedere ai tuoi account senza digitare una sola lettera." }, "secureUser": { - "message": "Level up your logins" + "message": "Porta i tuoi login al livello successivo" }, "secureUserBody": { - "message": "Use the generator to create and save strong, unique passwords for all your accounts." + "message": "Usa il generatore per creare e salvare password forti e uniche per tutti i tuoi account." }, "secureDevices": { - "message": "Your data, when and where you need it" + "message": "I tuoi dati, dove e quando ti servono" }, "secureDevicesBody": { - "message": "Save unlimited passwords across unlimited devices with Bitwarden mobile, browser, and desktop apps." + "message": "Salva tutte le password che vuoi su un numero illimitato di dispositivi con le app Bitwarden per browser, mobile e desktop." + }, + "nudgeBadgeAria": { + "message": "1 notifica" + }, + "emptyVaultNudgeTitle": { + "message": "Importa password esistenti" + }, + "emptyVaultNudgeBody": { + "message": "Usa l'importatore per trasferire rapidamente i login su Bitwarden senza aggiungerli manualmente." + }, + "emptyVaultNudgeButton": { + "message": "Importa ora" + }, + "hasItemsVaultNudgeTitle": { + "message": "Benvenuto nella tua cassaforte!" + }, + "hasItemsVaultNudgeBodyOne": { + "message": "Riempimento automatico per questa pagina" + }, + "hasItemsVaultNudgeBodyTwo": { + "message": "Trova facilmente gli elementi più usati grazie ai Preferiti" + }, + "hasItemsVaultNudgeBodyThree": { + "message": "Cerca altro nella tua cassaforte" + }, + "newLoginNudgeTitle": { + "message": "Accedi in un attimo grazie al riempimento automatico" + }, + "newLoginNudgeBodyOne": { + "message": "Includi", + "description": "This is in multiple parts to allow for bold text in the middle of the sentence.", + "example": "Include a Website so this login appears as an autofill suggestion." + }, + "newLoginNudgeBodyBold": { + "message": "Sito", + "description": "This is in multiple parts to allow for bold text in the middle of the sentence.", + "example": "Include a Website so this login appears as an autofill suggestion." + }, + "newLoginNudgeBodyTwo": { + "message": "questo login appare come suggerimento per il riempimento automatico.", + "description": "This is in multiple parts to allow for bold text in the middle of the sentence.", + "example": "Include a Website so this login appears as an autofill suggestion." + }, + "newCardNudgeTitle": { + "message": "Accesso e pagamento online semplificati" + }, + "newCardNudgeBody": { + "message": "Con le carte memorizzate, riempi i campi di pagamento in modo facile e veloce." + }, + "newIdentityNudgeTitle": { + "message": "Semplifica la creazione di account" + }, + "newIdentityNudgeBody": { + "message": "Con le identità, riempi in un attimo i moduli di registrazione per la creazione di account." + }, + "newNoteNudgeTitle": { + "message": "Mantieni al sicuro i tuoi dati sensibili" + }, + "newNoteNudgeBody": { + "message": "Con le note, memorizzi in modo sicuro i dati sensibili come i dettagli bancari o assicurativi." + }, + "newSshNudgeTitle": { + "message": "Accesso SSH ideale per gli sviluppatori" + }, + "newSshNudgeBodyOne": { + "message": "Memorizza le chiavi e connettiti con l'agente SSH per un'autenticazione crittografata veloce.", + "description": "Two part message", + "example": "Store your keys and connect with the SSH agent for fast, encrypted authentication. Learn more about SSH agent" + }, + "newSshNudgeBodyTwo": { + "message": "Scopri di più sull'agente SSH", + "description": "Two part message", + "example": "Store your keys and connect with the SSH agent for fast, encrypted authentication. Learn more about SSH agent" + }, + "generatorNudgeTitle": { + "message": "Crea rapidamente password sicure" + }, + "generatorNudgeBodyOne": { + "message": "Crea facilmente password forti e uniche cliccando su", + "description": "Two part message", + "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." + }, + "generatorNudgeBodyTwo": { + "message": "per aiutarti a mantenere i tuoi login al sicuro.", + "description": "Two part message", + "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." + }, + "generatorNudgeBodyAria": { + "message": "Crea facilmente password forti e uniche cliccando sul pulsante Genera password per aiutarti a mantenere al sicuro i tuoi login.", + "description": "Aria label for the body content of the generator nudge" + }, + "noPermissionsViewPage": { + "message": "Non hai i permessi per visualizzare questa pagina. Prova ad accedere con un altro account." } } diff --git a/apps/browser/src/_locales/ja/messages.json b/apps/browser/src/_locales/ja/messages.json index 34b15699437..43fb9621f8f 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,55 @@ "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 内で更新しました。", + "notificationNewItemAria": { + "message": "New Item, opens in new window", + "description": "Aria label for the new item button in notification bar confirmation message when error is prompted" + }, + "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": { - "content": "$1" + "label": { + "content": "$1", + "example": "Login" } - }, - "description": "Shown to user after login is updated." + } + }, + "notificationLoginSaveConfirmation": { + "message": "saved to Bitwarden.", + "description": "Shown to user after item is saved." + }, + "notificationLoginUpdatedConfirmation": { + "message": "updated in Bitwarden.", + "description": "Shown to user after item is updated." + }, + "selectItemAriaLabel": { + "message": "Select $ITEMTYPE$, $ITEMNAME$", + "description": "Used by screen readers. $1 is the item type (like vault or folder), $2 is the selected item name.", + "placeholders": { + "itemType": { + "content": "$1" + }, + "itemName": { + "content": "$2" + } + } }, "saveAsNewLoginAction": { "message": "新規のログイン情報として保存", @@ -1082,12 +1120,16 @@ "message": "ログイン情報を更新", "description": "Button text for updating an existing login entry." }, - "saveLoginPrompt": { - "message": "ログイン情報を保存しますか?", + "unlockToSave": { + "message": "Unlock to save this login", + "description": "User prompt to take action in order to save the login they just entered." + }, + "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": { @@ -1323,8 +1365,8 @@ "featureUnavailable": { "message": "サービスが利用できません" }, - "encryptionKeyMigrationRequired": { - "message": "暗号化キーの移行が必要です。暗号化キーを更新するには、ウェブ保管庫からログインしてください。" + "legacyEncryptionUnsupported": { + "message": "Legacy encryption is no longer supported. Please contact support to recover your account." }, "premiumMembership": { "message": "プレミアム会員" @@ -1557,6 +1599,24 @@ "autofillSuggestionsSectionTitle": { "message": "自動入力の候補" }, + "autofillSpotlightTitle": { + "message": "Easily find autofill suggestions" + }, + "autofillSpotlightDesc": { + "message": "Turn off your browser's autofill settings, so they don't conflict with Bitwarden." + }, + "turnOffBrowserAutofill": { + "message": "Turn off $BROWSER$ autofill", + "placeholders": { + "browser": { + "content": "$1", + "example": "Chrome" + } + } + }, + "turnOffAutofill": { + "message": "Turn off autofill" + }, "showInlineMenuLabel": { "message": "フォームフィールドに自動入力の候補を表示する" }, @@ -2144,6 +2204,9 @@ "useThisPassword": { "message": "このパスワードを使用する" }, + "useThisPassphrase": { + "message": "Use this passphrase" + }, "useThisUsername": { "message": "このユーザー名を使用する" }, @@ -2167,15 +2230,6 @@ "vaultTimeoutAction1": { "message": "タイムアウト時のアクション" }, - "newCustomizationOptionsCalloutTitle": { - "message": "新しいカスタマイズオプション" - }, - "newCustomizationOptionsCalloutContent": { - "message": "クイックコピーやコンパクトモードなどで保管庫をカスタマイズできます!" - }, - "newCustomizationOptionsCalloutLink": { - "message": "すべての外観設定を表示" - }, "lock": { "message": "ロック", "description": "Verb form: to make secure or inaccessible by" @@ -2623,6 +2677,10 @@ "message": "すべての Send", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, + "maxAccessCountReached": { + "message": "Max access count reached", + "description": "This text will be displayed after a Send has been accessed the maximum amount of times." + }, "hideTextByDefault": { "message": "デフォルトでテキストを隠す" }, @@ -2963,14 +3021,14 @@ "copyCustomFieldNameNotUnique": { "message": "一意の識別子が見つかりませんでした。" }, - "convertOrganizationEncryptionDesc": { - "message": "$ORGANIZATION$ は自己ホストの鍵サーバで SSO を使用しています。この組織のメンバーのログインにマスターパスワードは必要ありません。", - "placeholders": { - "organization": { - "content": "$1", - "example": "My Org Name" - } - } + "removeMasterPasswordForOrganizationUserKeyConnector": { + "message": "A master password is no longer required for members of the following organization. Please confirm the domain below with your organization administrator." + }, + "organizationName": { + "message": "Organization name" + }, + "keyConnectorDomain": { + "message": "Key Connector domain" }, "leaveOrganization": { "message": "組織から脱退する" @@ -3532,12 +3590,36 @@ "deviceTrusted": { "message": "信頼されたデバイス" }, - "sendsNoItemsTitle": { - "message": "アクティブな Send なし", + "trustOrganization": { + "message": "Trust organization" + }, + "trust": { + "message": "Trust" + }, + "doNotTrust": { + "message": "Do not trust" + }, + "organizationNotTrusted": { + "message": "Organization is not trusted" + }, + "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" + }, + "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." + }, + "orgTrustWarning1": { + "message": "This organization has an Enterprise policy that will enroll you in account recovery. Enrollment will allow organization administrators to change your password. Only proceed if you recognize this organization and the fingerprint phrase displayed below matches the organization's fingerprint." + }, + "trustUser": { + "message": "Trust user" + }, + "sendsTitleNoItems": { + "message": "Send sensitive information safely", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, - "sendsNoItemsMessage": { - "message": "Send を使用すると暗号化された情報を誰とでも安全に共有できます。", + "sendsBodyNoItems": { + "message": "Share files and data securely with anyone, on any platform. Your information will remain end-to-end encrypted while limiting exposure.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "inputRequired": { @@ -4456,6 +4538,33 @@ } } }, + "downloadBitwarden": { + "message": "Download Bitwarden" + }, + "downloadBitwardenOnAllDevices": { + "message": "Download Bitwarden on all devices" + }, + "getTheMobileApp": { + "message": "Get the mobile app" + }, + "getTheMobileAppDesc": { + "message": "Access your passwords on the go with the Bitwarden mobile app." + }, + "getTheDesktopApp": { + "message": "Get the desktop app" + }, + "getTheDesktopAppDesc": { + "message": "Access your vault without a browser, then set up unlock with biometrics to expedite unlocking in both the desktop app and browser extension." + }, + "downloadFromBitwardenNow": { + "message": "Download from bitwarden.com now" + }, + "getItOnGooglePlay": { + "message": "Get it on Google Play" + }, + "downloadOnTheAppStore": { + "message": "Download on the App Store" + }, "permanentlyDeleteAttachmentConfirmation": { "message": "この添付ファイルを完全に削除してもよろしいですか?" }, @@ -4917,6 +5026,15 @@ "biometricsStatusHelptextUnavailableReasonUnknown": { "message": "生体認証によるロック解除は、不明な理由により現在利用できません。" }, + "unlockVault": { + "message": "Unlock your vault in seconds" + }, + "unlockVaultDesc": { + "message": "You can customize your unlock and timeout settings to more quickly access your vault." + }, + "unlockPinSet": { + "message": "Unlock PIN set" + }, "authenticating": { "message": "認証中" }, @@ -4928,8 +5046,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": { @@ -5079,42 +5197,6 @@ "beta": { "message": "ベータ" }, - "importantNotice": { - "message": "重要なお知らせ" - }, - "setupTwoStepLogin": { - "message": "2段階認証を設定する" - }, - "newDeviceVerificationNoticeContentPage1": { - "message": "Bitwarden は2025年2月以降、新しいデバイスからのログイン時にアカウントのメールアドレスに確認コードを送信します。" - }, - "newDeviceVerificationNoticeContentPage2": { - "message": "代わりに2段階認証によるログインでアカウントを保護するか、メールアドレスをあなたがアクセスできるものに変更できます。" - }, - "remindMeLater": { - "message": "後で再通知" - }, - "newDeviceVerificationNoticePageOneFormContent": { - "message": "メールアドレス $EMAIL$ は、確実にアクセスできるものですか?", - "placeholders": { - "email": { - "content": "$1", - "example": "your_name@email.com" - } - } - }, - "newDeviceVerificationNoticePageOneEmailAccessNo": { - "message": "いいえ、違います。" - }, - "newDeviceVerificationNoticePageOneEmailAccessYes": { - "message": "はい、メールアドレスには私が確実にアクセスできます" - }, - "turnOnTwoStepLogin": { - "message": "2段階認証によるログインを有効にする" - }, - "changeAcctEmail": { - "message": "アカウントのメールアドレスを変更する" - }, "extensionWidth": { "message": "拡張機能の幅" }, @@ -5169,6 +5251,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 +5283,98 @@ }, "secureDevicesBody": { "message": "Bitwarden のモバイル、ブラウザ、デスクトップアプリでは、保存できるパスワード数やデバイス数に制限はありません。" + }, + "nudgeBadgeAria": { + "message": "1 notification" + }, + "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!" + }, + "hasItemsVaultNudgeBodyOne": { + "message": "Autofill items for the current page" + }, + "hasItemsVaultNudgeBodyTwo": { + "message": "Favorite items for easy access" + }, + "hasItemsVaultNudgeBodyThree": { + "message": "Search your vault for something else" + }, + "newLoginNudgeTitle": { + "message": "Save time with autofill" + }, + "newLoginNudgeBodyOne": { + "message": "Include a", + "description": "This is in multiple parts to allow for bold text in the middle of the sentence.", + "example": "Include a Website so this login appears as an autofill suggestion." + }, + "newLoginNudgeBodyBold": { + "message": "Website", + "description": "This is in multiple parts to allow for bold text in the middle of the sentence.", + "example": "Include a Website so this login appears as an autofill suggestion." + }, + "newLoginNudgeBodyTwo": { + "message": "so this login appears as an autofill suggestion.", + "description": "This is in multiple parts to allow for bold text in the middle of the sentence.", + "example": "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" + }, + "newSshNudgeBodyOne": { + "message": "Store your keys and connect with the SSH agent for fast, encrypted authentication.", + "description": "Two part message", + "example": "Store your keys and connect with the SSH agent for fast, encrypted authentication. Learn more about SSH agent" + }, + "newSshNudgeBodyTwo": { + "message": "Learn more about SSH agent", + "description": "Two part message", + "example": "Store your keys and connect with the SSH agent for fast, encrypted authentication. Learn more about SSH agent" + }, + "generatorNudgeTitle": { + "message": "Quickly create passwords" + }, + "generatorNudgeBodyOne": { + "message": "Easily create strong and unique passwords by clicking on", + "description": "Two part message", + "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." + }, + "generatorNudgeBodyTwo": { + "message": "to help you keep your logins secure.", + "description": "Two part message", + "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." + }, + "generatorNudgeBodyAria": { + "message": "Easily create strong and unique passwords by clicking on the Generate password button to help you keep your logins secure.", + "description": "Aria label for the body content of the generator nudge" + }, + "noPermissionsViewPage": { + "message": "You do not have permissions to view this page. Try logging in with a different account." } } diff --git a/apps/browser/src/_locales/ka/messages.json b/apps/browser/src/_locales/ka/messages.json index f261f69904d..8789bada8d1 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,55 @@ "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.", + "notificationNewItemAria": { + "message": "New Item, opens in new window", + "description": "Aria label for the new item button in notification bar confirmation message when error is prompted" + }, + "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": { - "content": "$1" + "label": { + "content": "$1", + "example": "Login" } - }, - "description": "Shown to user after login is updated." + } + }, + "notificationLoginSaveConfirmation": { + "message": "saved to Bitwarden.", + "description": "Shown to user after item is saved." + }, + "notificationLoginUpdatedConfirmation": { + "message": "updated in Bitwarden.", + "description": "Shown to user after item is updated." + }, + "selectItemAriaLabel": { + "message": "Select $ITEMTYPE$, $ITEMNAME$", + "description": "Used by screen readers. $1 is the item type (like vault or folder), $2 is the selected item name.", + "placeholders": { + "itemType": { + "content": "$1" + }, + "itemName": { + "content": "$2" + } + } }, "saveAsNewLoginAction": { "message": "Save as new login", @@ -1082,12 +1120,16 @@ "message": "Update login", "description": "Button text for updating an existing login entry." }, - "saveLoginPrompt": { - "message": "Save login?", + "unlockToSave": { + "message": "Unlock to save this login", + "description": "User prompt to take action in order to save the login they just entered." + }, + "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": { @@ -1323,8 +1365,8 @@ "featureUnavailable": { "message": "Feature unavailable" }, - "encryptionKeyMigrationRequired": { - "message": "Encryption key migration required. Please login through the web vault to update your encryption key." + "legacyEncryptionUnsupported": { + "message": "Legacy encryption is no longer supported. Please contact support to recover your account." }, "premiumMembership": { "message": "Premium membership" @@ -1557,6 +1599,24 @@ "autofillSuggestionsSectionTitle": { "message": "Autofill suggestions" }, + "autofillSpotlightTitle": { + "message": "Easily find autofill suggestions" + }, + "autofillSpotlightDesc": { + "message": "Turn off your browser's autofill settings, so they don't conflict with Bitwarden." + }, + "turnOffBrowserAutofill": { + "message": "Turn off $BROWSER$ autofill", + "placeholders": { + "browser": { + "content": "$1", + "example": "Chrome" + } + } + }, + "turnOffAutofill": { + "message": "Turn off autofill" + }, "showInlineMenuLabel": { "message": "Show autofill suggestions on form fields" }, @@ -2144,6 +2204,9 @@ "useThisPassword": { "message": "Use this password" }, + "useThisPassphrase": { + "message": "Use this passphrase" + }, "useThisUsername": { "message": "Use this username" }, @@ -2167,15 +2230,6 @@ "vaultTimeoutAction1": { "message": "Timeout action" }, - "newCustomizationOptionsCalloutTitle": { - "message": "New customization options" - }, - "newCustomizationOptionsCalloutContent": { - "message": "Customize your vault experience with quick copy actions, compact mode, and more!" - }, - "newCustomizationOptionsCalloutLink": { - "message": "View all Appearance settings" - }, "lock": { "message": "ჩაკეტვა", "description": "Verb form: to make secure or inaccessible by" @@ -2623,6 +2677,10 @@ "message": "All Sends", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, + "maxAccessCountReached": { + "message": "Max access count reached", + "description": "This text will be displayed after a Send has been accessed the maximum amount of times." + }, "hideTextByDefault": { "message": "Hide text by default" }, @@ -2963,14 +3021,14 @@ "copyCustomFieldNameNotUnique": { "message": "No unique identifier found." }, - "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.", - "placeholders": { - "organization": { - "content": "$1", - "example": "My Org Name" - } - } + "removeMasterPasswordForOrganizationUserKeyConnector": { + "message": "A master password is no longer required for members of the following organization. Please confirm the domain below with your organization administrator." + }, + "organizationName": { + "message": "Organization name" + }, + "keyConnectorDomain": { + "message": "Key Connector domain" }, "leaveOrganization": { "message": "Leave organization" @@ -3532,12 +3590,36 @@ "deviceTrusted": { "message": "Device trusted" }, - "sendsNoItemsTitle": { - "message": "No active Sends", + "trustOrganization": { + "message": "Trust organization" + }, + "trust": { + "message": "Trust" + }, + "doNotTrust": { + "message": "Do not trust" + }, + "organizationNotTrusted": { + "message": "Organization is not trusted" + }, + "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" + }, + "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." + }, + "orgTrustWarning1": { + "message": "This organization has an Enterprise policy that will enroll you in account recovery. Enrollment will allow organization administrators to change your password. Only proceed if you recognize this organization and the fingerprint phrase displayed below matches the organization's fingerprint." + }, + "trustUser": { + "message": "Trust user" + }, + "sendsTitleNoItems": { + "message": "Send sensitive information safely", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, - "sendsNoItemsMessage": { - "message": "Use Send to securely share encrypted information with anyone.", + "sendsBodyNoItems": { + "message": "Share files and data securely with anyone, on any platform. Your information will remain end-to-end encrypted while limiting exposure.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "inputRequired": { @@ -4456,6 +4538,33 @@ } } }, + "downloadBitwarden": { + "message": "Download Bitwarden" + }, + "downloadBitwardenOnAllDevices": { + "message": "Download Bitwarden on all devices" + }, + "getTheMobileApp": { + "message": "Get the mobile app" + }, + "getTheMobileAppDesc": { + "message": "Access your passwords on the go with the Bitwarden mobile app." + }, + "getTheDesktopApp": { + "message": "Get the desktop app" + }, + "getTheDesktopAppDesc": { + "message": "Access your vault without a browser, then set up unlock with biometrics to expedite unlocking in both the desktop app and browser extension." + }, + "downloadFromBitwardenNow": { + "message": "Download from bitwarden.com now" + }, + "getItOnGooglePlay": { + "message": "Get it on Google Play" + }, + "downloadOnTheAppStore": { + "message": "Download on the App Store" + }, "permanentlyDeleteAttachmentConfirmation": { "message": "Are you sure you want to permanently delete this attachment?" }, @@ -4917,6 +5026,15 @@ "biometricsStatusHelptextUnavailableReasonUnknown": { "message": "Biometric unlock is currently unavailable for an unknown reason." }, + "unlockVault": { + "message": "Unlock your vault in seconds" + }, + "unlockVaultDesc": { + "message": "You can customize your unlock and timeout settings to more quickly access your vault." + }, + "unlockPinSet": { + "message": "Unlock PIN set" + }, "authenticating": { "message": "ავთენტიკაცია" }, @@ -4928,8 +5046,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": { @@ -5079,42 +5197,6 @@ "beta": { "message": "Beta" }, - "importantNotice": { - "message": "Important notice" - }, - "setupTwoStepLogin": { - "message": "Set up two-step login" - }, - "newDeviceVerificationNoticeContentPage1": { - "message": "Bitwarden will send a code to your account email to verify logins from new devices starting in February 2025." - }, - "newDeviceVerificationNoticeContentPage2": { - "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" - }, - "newDeviceVerificationNoticePageOneFormContent": { - "message": "Do you have reliable access to your email, $EMAIL$?", - "placeholders": { - "email": { - "content": "$1", - "example": "your_name@email.com" - } - } - }, - "newDeviceVerificationNoticePageOneEmailAccessNo": { - "message": "No, I do not" - }, - "newDeviceVerificationNoticePageOneEmailAccessYes": { - "message": "Yes, I can reliably access my email" - }, - "turnOnTwoStepLogin": { - "message": "Turn on two-step login" - }, - "changeAcctEmail": { - "message": "Change account email" - }, "extensionWidth": { "message": "Extension width" }, @@ -5169,6 +5251,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 +5283,98 @@ }, "secureDevicesBody": { "message": "Save unlimited passwords across unlimited devices with Bitwarden mobile, browser, and desktop apps." + }, + "nudgeBadgeAria": { + "message": "1 notification" + }, + "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!" + }, + "hasItemsVaultNudgeBodyOne": { + "message": "Autofill items for the current page" + }, + "hasItemsVaultNudgeBodyTwo": { + "message": "Favorite items for easy access" + }, + "hasItemsVaultNudgeBodyThree": { + "message": "Search your vault for something else" + }, + "newLoginNudgeTitle": { + "message": "Save time with autofill" + }, + "newLoginNudgeBodyOne": { + "message": "Include a", + "description": "This is in multiple parts to allow for bold text in the middle of the sentence.", + "example": "Include a Website so this login appears as an autofill suggestion." + }, + "newLoginNudgeBodyBold": { + "message": "Website", + "description": "This is in multiple parts to allow for bold text in the middle of the sentence.", + "example": "Include a Website so this login appears as an autofill suggestion." + }, + "newLoginNudgeBodyTwo": { + "message": "so this login appears as an autofill suggestion.", + "description": "This is in multiple parts to allow for bold text in the middle of the sentence.", + "example": "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" + }, + "newSshNudgeBodyOne": { + "message": "Store your keys and connect with the SSH agent for fast, encrypted authentication.", + "description": "Two part message", + "example": "Store your keys and connect with the SSH agent for fast, encrypted authentication. Learn more about SSH agent" + }, + "newSshNudgeBodyTwo": { + "message": "Learn more about SSH agent", + "description": "Two part message", + "example": "Store your keys and connect with the SSH agent for fast, encrypted authentication. Learn more about SSH agent" + }, + "generatorNudgeTitle": { + "message": "Quickly create passwords" + }, + "generatorNudgeBodyOne": { + "message": "Easily create strong and unique passwords by clicking on", + "description": "Two part message", + "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." + }, + "generatorNudgeBodyTwo": { + "message": "to help you keep your logins secure.", + "description": "Two part message", + "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." + }, + "generatorNudgeBodyAria": { + "message": "Easily create strong and unique passwords by clicking on the Generate password button to help you keep your logins secure.", + "description": "Aria label for the body content of the generator nudge" + }, + "noPermissionsViewPage": { + "message": "You do not have permissions to view this page. Try logging in with a different account." } } diff --git a/apps/browser/src/_locales/km/messages.json b/apps/browser/src/_locales/km/messages.json index 7bec167e1c6..032d8c89d49 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,55 @@ "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.", + "notificationNewItemAria": { + "message": "New Item, opens in new window", + "description": "Aria label for the new item button in notification bar confirmation message when error is prompted" + }, + "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": { - "content": "$1" + "label": { + "content": "$1", + "example": "Login" } - }, - "description": "Shown to user after login is updated." + } + }, + "notificationLoginSaveConfirmation": { + "message": "saved to Bitwarden.", + "description": "Shown to user after item is saved." + }, + "notificationLoginUpdatedConfirmation": { + "message": "updated in Bitwarden.", + "description": "Shown to user after item is updated." + }, + "selectItemAriaLabel": { + "message": "Select $ITEMTYPE$, $ITEMNAME$", + "description": "Used by screen readers. $1 is the item type (like vault or folder), $2 is the selected item name.", + "placeholders": { + "itemType": { + "content": "$1" + }, + "itemName": { + "content": "$2" + } + } }, "saveAsNewLoginAction": { "message": "Save as new login", @@ -1082,12 +1120,16 @@ "message": "Update login", "description": "Button text for updating an existing login entry." }, - "saveLoginPrompt": { - "message": "Save login?", + "unlockToSave": { + "message": "Unlock to save this login", + "description": "User prompt to take action in order to save the login they just entered." + }, + "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": { @@ -1323,8 +1365,8 @@ "featureUnavailable": { "message": "Feature unavailable" }, - "encryptionKeyMigrationRequired": { - "message": "Encryption key migration required. Please login through the web vault to update your encryption key." + "legacyEncryptionUnsupported": { + "message": "Legacy encryption is no longer supported. Please contact support to recover your account." }, "premiumMembership": { "message": "Premium membership" @@ -1557,6 +1599,24 @@ "autofillSuggestionsSectionTitle": { "message": "Autofill suggestions" }, + "autofillSpotlightTitle": { + "message": "Easily find autofill suggestions" + }, + "autofillSpotlightDesc": { + "message": "Turn off your browser's autofill settings, so they don't conflict with Bitwarden." + }, + "turnOffBrowserAutofill": { + "message": "Turn off $BROWSER$ autofill", + "placeholders": { + "browser": { + "content": "$1", + "example": "Chrome" + } + } + }, + "turnOffAutofill": { + "message": "Turn off autofill" + }, "showInlineMenuLabel": { "message": "Show autofill suggestions on form fields" }, @@ -2144,6 +2204,9 @@ "useThisPassword": { "message": "Use this password" }, + "useThisPassphrase": { + "message": "Use this passphrase" + }, "useThisUsername": { "message": "Use this username" }, @@ -2167,15 +2230,6 @@ "vaultTimeoutAction1": { "message": "Timeout action" }, - "newCustomizationOptionsCalloutTitle": { - "message": "New customization options" - }, - "newCustomizationOptionsCalloutContent": { - "message": "Customize your vault experience with quick copy actions, compact mode, and more!" - }, - "newCustomizationOptionsCalloutLink": { - "message": "View all Appearance settings" - }, "lock": { "message": "Lock", "description": "Verb form: to make secure or inaccessible by" @@ -2623,6 +2677,10 @@ "message": "All Sends", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, + "maxAccessCountReached": { + "message": "Max access count reached", + "description": "This text will be displayed after a Send has been accessed the maximum amount of times." + }, "hideTextByDefault": { "message": "Hide text by default" }, @@ -2963,14 +3021,14 @@ "copyCustomFieldNameNotUnique": { "message": "No unique identifier found." }, - "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.", - "placeholders": { - "organization": { - "content": "$1", - "example": "My Org Name" - } - } + "removeMasterPasswordForOrganizationUserKeyConnector": { + "message": "A master password is no longer required for members of the following organization. Please confirm the domain below with your organization administrator." + }, + "organizationName": { + "message": "Organization name" + }, + "keyConnectorDomain": { + "message": "Key Connector domain" }, "leaveOrganization": { "message": "Leave organization" @@ -3532,12 +3590,36 @@ "deviceTrusted": { "message": "Device trusted" }, - "sendsNoItemsTitle": { - "message": "No active Sends", + "trustOrganization": { + "message": "Trust organization" + }, + "trust": { + "message": "Trust" + }, + "doNotTrust": { + "message": "Do not trust" + }, + "organizationNotTrusted": { + "message": "Organization is not trusted" + }, + "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" + }, + "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." + }, + "orgTrustWarning1": { + "message": "This organization has an Enterprise policy that will enroll you in account recovery. Enrollment will allow organization administrators to change your password. Only proceed if you recognize this organization and the fingerprint phrase displayed below matches the organization's fingerprint." + }, + "trustUser": { + "message": "Trust user" + }, + "sendsTitleNoItems": { + "message": "Send sensitive information safely", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, - "sendsNoItemsMessage": { - "message": "Use Send to securely share encrypted information with anyone.", + "sendsBodyNoItems": { + "message": "Share files and data securely with anyone, on any platform. Your information will remain end-to-end encrypted while limiting exposure.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "inputRequired": { @@ -4456,6 +4538,33 @@ } } }, + "downloadBitwarden": { + "message": "Download Bitwarden" + }, + "downloadBitwardenOnAllDevices": { + "message": "Download Bitwarden on all devices" + }, + "getTheMobileApp": { + "message": "Get the mobile app" + }, + "getTheMobileAppDesc": { + "message": "Access your passwords on the go with the Bitwarden mobile app." + }, + "getTheDesktopApp": { + "message": "Get the desktop app" + }, + "getTheDesktopAppDesc": { + "message": "Access your vault without a browser, then set up unlock with biometrics to expedite unlocking in both the desktop app and browser extension." + }, + "downloadFromBitwardenNow": { + "message": "Download from bitwarden.com now" + }, + "getItOnGooglePlay": { + "message": "Get it on Google Play" + }, + "downloadOnTheAppStore": { + "message": "Download on the App Store" + }, "permanentlyDeleteAttachmentConfirmation": { "message": "Are you sure you want to permanently delete this attachment?" }, @@ -4917,6 +5026,15 @@ "biometricsStatusHelptextUnavailableReasonUnknown": { "message": "Biometric unlock is currently unavailable for an unknown reason." }, + "unlockVault": { + "message": "Unlock your vault in seconds" + }, + "unlockVaultDesc": { + "message": "You can customize your unlock and timeout settings to more quickly access your vault." + }, + "unlockPinSet": { + "message": "Unlock PIN set" + }, "authenticating": { "message": "Authenticating" }, @@ -4928,8 +5046,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": { @@ -5079,42 +5197,6 @@ "beta": { "message": "Beta" }, - "importantNotice": { - "message": "Important notice" - }, - "setupTwoStepLogin": { - "message": "Set up two-step login" - }, - "newDeviceVerificationNoticeContentPage1": { - "message": "Bitwarden will send a code to your account email to verify logins from new devices starting in February 2025." - }, - "newDeviceVerificationNoticeContentPage2": { - "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" - }, - "newDeviceVerificationNoticePageOneFormContent": { - "message": "Do you have reliable access to your email, $EMAIL$?", - "placeholders": { - "email": { - "content": "$1", - "example": "your_name@email.com" - } - } - }, - "newDeviceVerificationNoticePageOneEmailAccessNo": { - "message": "No, I do not" - }, - "newDeviceVerificationNoticePageOneEmailAccessYes": { - "message": "Yes, I can reliably access my email" - }, - "turnOnTwoStepLogin": { - "message": "Turn on two-step login" - }, - "changeAcctEmail": { - "message": "Change account email" - }, "extensionWidth": { "message": "Extension width" }, @@ -5169,6 +5251,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 +5283,98 @@ }, "secureDevicesBody": { "message": "Save unlimited passwords across unlimited devices with Bitwarden mobile, browser, and desktop apps." + }, + "nudgeBadgeAria": { + "message": "1 notification" + }, + "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!" + }, + "hasItemsVaultNudgeBodyOne": { + "message": "Autofill items for the current page" + }, + "hasItemsVaultNudgeBodyTwo": { + "message": "Favorite items for easy access" + }, + "hasItemsVaultNudgeBodyThree": { + "message": "Search your vault for something else" + }, + "newLoginNudgeTitle": { + "message": "Save time with autofill" + }, + "newLoginNudgeBodyOne": { + "message": "Include a", + "description": "This is in multiple parts to allow for bold text in the middle of the sentence.", + "example": "Include a Website so this login appears as an autofill suggestion." + }, + "newLoginNudgeBodyBold": { + "message": "Website", + "description": "This is in multiple parts to allow for bold text in the middle of the sentence.", + "example": "Include a Website so this login appears as an autofill suggestion." + }, + "newLoginNudgeBodyTwo": { + "message": "so this login appears as an autofill suggestion.", + "description": "This is in multiple parts to allow for bold text in the middle of the sentence.", + "example": "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" + }, + "newSshNudgeBodyOne": { + "message": "Store your keys and connect with the SSH agent for fast, encrypted authentication.", + "description": "Two part message", + "example": "Store your keys and connect with the SSH agent for fast, encrypted authentication. Learn more about SSH agent" + }, + "newSshNudgeBodyTwo": { + "message": "Learn more about SSH agent", + "description": "Two part message", + "example": "Store your keys and connect with the SSH agent for fast, encrypted authentication. Learn more about SSH agent" + }, + "generatorNudgeTitle": { + "message": "Quickly create passwords" + }, + "generatorNudgeBodyOne": { + "message": "Easily create strong and unique passwords by clicking on", + "description": "Two part message", + "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." + }, + "generatorNudgeBodyTwo": { + "message": "to help you keep your logins secure.", + "description": "Two part message", + "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." + }, + "generatorNudgeBodyAria": { + "message": "Easily create strong and unique passwords by clicking on the Generate password button to help you keep your logins secure.", + "description": "Aria label for the body content of the generator nudge" + }, + "noPermissionsViewPage": { + "message": "You do not have permissions to view this page. Try logging in with a different account." } } diff --git a/apps/browser/src/_locales/kn/messages.json b/apps/browser/src/_locales/kn/messages.json index 7d9583dc652..411d9446390 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,55 @@ "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.", + "notificationNewItemAria": { + "message": "New Item, opens in new window", + "description": "Aria label for the new item button in notification bar confirmation message when error is prompted" + }, + "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": { - "content": "$1" + "label": { + "content": "$1", + "example": "Login" } - }, - "description": "Shown to user after login is updated." + } + }, + "notificationLoginSaveConfirmation": { + "message": "saved to Bitwarden.", + "description": "Shown to user after item is saved." + }, + "notificationLoginUpdatedConfirmation": { + "message": "updated in Bitwarden.", + "description": "Shown to user after item is updated." + }, + "selectItemAriaLabel": { + "message": "Select $ITEMTYPE$, $ITEMNAME$", + "description": "Used by screen readers. $1 is the item type (like vault or folder), $2 is the selected item name.", + "placeholders": { + "itemType": { + "content": "$1" + }, + "itemName": { + "content": "$2" + } + } }, "saveAsNewLoginAction": { "message": "Save as new login", @@ -1082,12 +1120,16 @@ "message": "Update login", "description": "Button text for updating an existing login entry." }, - "saveLoginPrompt": { - "message": "Save login?", + "unlockToSave": { + "message": "Unlock to save this login", + "description": "User prompt to take action in order to save the login they just entered." + }, + "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": { @@ -1323,8 +1365,8 @@ "featureUnavailable": { "message": "ವೈಶಿಷ್ಟ್ಯ ಲಭ್ಯವಿಲ್ಲ" }, - "encryptionKeyMigrationRequired": { - "message": "Encryption key migration required. Please login through the web vault to update your encryption key." + "legacyEncryptionUnsupported": { + "message": "Legacy encryption is no longer supported. Please contact support to recover your account." }, "premiumMembership": { "message": "ಪ್ರೀಮಿಯಂ ಸದಸ್ಯತ್ವ" @@ -1557,6 +1599,24 @@ "autofillSuggestionsSectionTitle": { "message": "Autofill suggestions" }, + "autofillSpotlightTitle": { + "message": "Easily find autofill suggestions" + }, + "autofillSpotlightDesc": { + "message": "Turn off your browser's autofill settings, so they don't conflict with Bitwarden." + }, + "turnOffBrowserAutofill": { + "message": "Turn off $BROWSER$ autofill", + "placeholders": { + "browser": { + "content": "$1", + "example": "Chrome" + } + } + }, + "turnOffAutofill": { + "message": "Turn off autofill" + }, "showInlineMenuLabel": { "message": "Show autofill suggestions on form fields" }, @@ -2144,6 +2204,9 @@ "useThisPassword": { "message": "Use this password" }, + "useThisPassphrase": { + "message": "Use this passphrase" + }, "useThisUsername": { "message": "Use this username" }, @@ -2167,15 +2230,6 @@ "vaultTimeoutAction1": { "message": "Timeout action" }, - "newCustomizationOptionsCalloutTitle": { - "message": "New customization options" - }, - "newCustomizationOptionsCalloutContent": { - "message": "Customize your vault experience with quick copy actions, compact mode, and more!" - }, - "newCustomizationOptionsCalloutLink": { - "message": "View all Appearance settings" - }, "lock": { "message": "ಲಾಕ್‌", "description": "Verb form: to make secure or inaccessible by" @@ -2623,6 +2677,10 @@ "message": "ಎಲ್ಲಾ ಕಳುಹಿಸುತ್ತದೆ", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, + "maxAccessCountReached": { + "message": "Max access count reached", + "description": "This text will be displayed after a Send has been accessed the maximum amount of times." + }, "hideTextByDefault": { "message": "Hide text by default" }, @@ -2963,14 +3021,14 @@ "copyCustomFieldNameNotUnique": { "message": "No unique identifier found." }, - "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.", - "placeholders": { - "organization": { - "content": "$1", - "example": "My Org Name" - } - } + "removeMasterPasswordForOrganizationUserKeyConnector": { + "message": "A master password is no longer required for members of the following organization. Please confirm the domain below with your organization administrator." + }, + "organizationName": { + "message": "Organization name" + }, + "keyConnectorDomain": { + "message": "Key Connector domain" }, "leaveOrganization": { "message": "Leave organization" @@ -3532,12 +3590,36 @@ "deviceTrusted": { "message": "Device trusted" }, - "sendsNoItemsTitle": { - "message": "No active Sends", + "trustOrganization": { + "message": "Trust organization" + }, + "trust": { + "message": "Trust" + }, + "doNotTrust": { + "message": "Do not trust" + }, + "organizationNotTrusted": { + "message": "Organization is not trusted" + }, + "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" + }, + "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." + }, + "orgTrustWarning1": { + "message": "This organization has an Enterprise policy that will enroll you in account recovery. Enrollment will allow organization administrators to change your password. Only proceed if you recognize this organization and the fingerprint phrase displayed below matches the organization's fingerprint." + }, + "trustUser": { + "message": "Trust user" + }, + "sendsTitleNoItems": { + "message": "Send sensitive information safely", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, - "sendsNoItemsMessage": { - "message": "Use Send to securely share encrypted information with anyone.", + "sendsBodyNoItems": { + "message": "Share files and data securely with anyone, on any platform. Your information will remain end-to-end encrypted while limiting exposure.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "inputRequired": { @@ -4456,6 +4538,33 @@ } } }, + "downloadBitwarden": { + "message": "Download Bitwarden" + }, + "downloadBitwardenOnAllDevices": { + "message": "Download Bitwarden on all devices" + }, + "getTheMobileApp": { + "message": "Get the mobile app" + }, + "getTheMobileAppDesc": { + "message": "Access your passwords on the go with the Bitwarden mobile app." + }, + "getTheDesktopApp": { + "message": "Get the desktop app" + }, + "getTheDesktopAppDesc": { + "message": "Access your vault without a browser, then set up unlock with biometrics to expedite unlocking in both the desktop app and browser extension." + }, + "downloadFromBitwardenNow": { + "message": "Download from bitwarden.com now" + }, + "getItOnGooglePlay": { + "message": "Get it on Google Play" + }, + "downloadOnTheAppStore": { + "message": "Download on the App Store" + }, "permanentlyDeleteAttachmentConfirmation": { "message": "Are you sure you want to permanently delete this attachment?" }, @@ -4917,6 +5026,15 @@ "biometricsStatusHelptextUnavailableReasonUnknown": { "message": "Biometric unlock is currently unavailable for an unknown reason." }, + "unlockVault": { + "message": "Unlock your vault in seconds" + }, + "unlockVaultDesc": { + "message": "You can customize your unlock and timeout settings to more quickly access your vault." + }, + "unlockPinSet": { + "message": "Unlock PIN set" + }, "authenticating": { "message": "Authenticating" }, @@ -4928,8 +5046,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": { @@ -5079,42 +5197,6 @@ "beta": { "message": "Beta" }, - "importantNotice": { - "message": "Important notice" - }, - "setupTwoStepLogin": { - "message": "Set up two-step login" - }, - "newDeviceVerificationNoticeContentPage1": { - "message": "Bitwarden will send a code to your account email to verify logins from new devices starting in February 2025." - }, - "newDeviceVerificationNoticeContentPage2": { - "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" - }, - "newDeviceVerificationNoticePageOneFormContent": { - "message": "Do you have reliable access to your email, $EMAIL$?", - "placeholders": { - "email": { - "content": "$1", - "example": "your_name@email.com" - } - } - }, - "newDeviceVerificationNoticePageOneEmailAccessNo": { - "message": "No, I do not" - }, - "newDeviceVerificationNoticePageOneEmailAccessYes": { - "message": "Yes, I can reliably access my email" - }, - "turnOnTwoStepLogin": { - "message": "Turn on two-step login" - }, - "changeAcctEmail": { - "message": "Change account email" - }, "extensionWidth": { "message": "Extension width" }, @@ -5169,6 +5251,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 +5283,98 @@ }, "secureDevicesBody": { "message": "Save unlimited passwords across unlimited devices with Bitwarden mobile, browser, and desktop apps." + }, + "nudgeBadgeAria": { + "message": "1 notification" + }, + "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!" + }, + "hasItemsVaultNudgeBodyOne": { + "message": "Autofill items for the current page" + }, + "hasItemsVaultNudgeBodyTwo": { + "message": "Favorite items for easy access" + }, + "hasItemsVaultNudgeBodyThree": { + "message": "Search your vault for something else" + }, + "newLoginNudgeTitle": { + "message": "Save time with autofill" + }, + "newLoginNudgeBodyOne": { + "message": "Include a", + "description": "This is in multiple parts to allow for bold text in the middle of the sentence.", + "example": "Include a Website so this login appears as an autofill suggestion." + }, + "newLoginNudgeBodyBold": { + "message": "Website", + "description": "This is in multiple parts to allow for bold text in the middle of the sentence.", + "example": "Include a Website so this login appears as an autofill suggestion." + }, + "newLoginNudgeBodyTwo": { + "message": "so this login appears as an autofill suggestion.", + "description": "This is in multiple parts to allow for bold text in the middle of the sentence.", + "example": "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" + }, + "newSshNudgeBodyOne": { + "message": "Store your keys and connect with the SSH agent for fast, encrypted authentication.", + "description": "Two part message", + "example": "Store your keys and connect with the SSH agent for fast, encrypted authentication. Learn more about SSH agent" + }, + "newSshNudgeBodyTwo": { + "message": "Learn more about SSH agent", + "description": "Two part message", + "example": "Store your keys and connect with the SSH agent for fast, encrypted authentication. Learn more about SSH agent" + }, + "generatorNudgeTitle": { + "message": "Quickly create passwords" + }, + "generatorNudgeBodyOne": { + "message": "Easily create strong and unique passwords by clicking on", + "description": "Two part message", + "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." + }, + "generatorNudgeBodyTwo": { + "message": "to help you keep your logins secure.", + "description": "Two part message", + "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." + }, + "generatorNudgeBodyAria": { + "message": "Easily create strong and unique passwords by clicking on the Generate password button to help you keep your logins secure.", + "description": "Aria label for the body content of the generator nudge" + }, + "noPermissionsViewPage": { + "message": "You do not have permissions to view this page. Try logging in with a different account." } } diff --git a/apps/browser/src/_locales/ko/messages.json b/apps/browser/src/_locales/ko/messages.json index 7d65686df5d..f3af50ef779 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,55 @@ "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.", + "notificationNewItemAria": { + "message": "New Item, opens in new window", + "description": "Aria label for the new item button in notification bar confirmation message when error is prompted" + }, + "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": { - "content": "$1" + "label": { + "content": "$1", + "example": "Login" } - }, - "description": "Shown to user after login is updated." + } + }, + "notificationLoginSaveConfirmation": { + "message": "saved to Bitwarden.", + "description": "Shown to user after item is saved." + }, + "notificationLoginUpdatedConfirmation": { + "message": "updated in Bitwarden.", + "description": "Shown to user after item is updated." + }, + "selectItemAriaLabel": { + "message": "Select $ITEMTYPE$, $ITEMNAME$", + "description": "Used by screen readers. $1 is the item type (like vault or folder), $2 is the selected item name.", + "placeholders": { + "itemType": { + "content": "$1" + }, + "itemName": { + "content": "$2" + } + } }, "saveAsNewLoginAction": { "message": "Save as new login", @@ -1082,12 +1120,16 @@ "message": "Update login", "description": "Button text for updating an existing login entry." }, - "saveLoginPrompt": { - "message": "Save login?", + "unlockToSave": { + "message": "Unlock to save this login", + "description": "User prompt to take action in order to save the login they just entered." + }, + "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": { @@ -1323,8 +1365,8 @@ "featureUnavailable": { "message": "기능 사용할 수 없음" }, - "encryptionKeyMigrationRequired": { - "message": "암호화 키 마이그레이션이 필요합니다. 웹 볼트를 통해 로그인하여 암호화 키를 업데이트하세요." + "legacyEncryptionUnsupported": { + "message": "Legacy encryption is no longer supported. Please contact support to recover your account." }, "premiumMembership": { "message": "프리미엄 멤버십" @@ -1557,6 +1599,24 @@ "autofillSuggestionsSectionTitle": { "message": "자동 완성 제안" }, + "autofillSpotlightTitle": { + "message": "Easily find autofill suggestions" + }, + "autofillSpotlightDesc": { + "message": "Turn off your browser's autofill settings, so they don't conflict with Bitwarden." + }, + "turnOffBrowserAutofill": { + "message": "Turn off $BROWSER$ autofill", + "placeholders": { + "browser": { + "content": "$1", + "example": "Chrome" + } + } + }, + "turnOffAutofill": { + "message": "Turn off autofill" + }, "showInlineMenuLabel": { "message": "양식 필드에 자동 완성 제안 표시" }, @@ -2144,6 +2204,9 @@ "useThisPassword": { "message": "이 비밀번호 사용" }, + "useThisPassphrase": { + "message": "Use this passphrase" + }, "useThisUsername": { "message": "이 사용자 이름 사용" }, @@ -2167,15 +2230,6 @@ "vaultTimeoutAction1": { "message": "시간초과 시 행동" }, - "newCustomizationOptionsCalloutTitle": { - "message": "New customization options" - }, - "newCustomizationOptionsCalloutContent": { - "message": "Customize your vault experience with quick copy actions, compact mode, and more!" - }, - "newCustomizationOptionsCalloutLink": { - "message": "View all Appearance settings" - }, "lock": { "message": "잠금", "description": "Verb form: to make secure or inaccessible by" @@ -2623,6 +2677,10 @@ "message": "모든 Send", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, + "maxAccessCountReached": { + "message": "Max access count reached", + "description": "This text will be displayed after a Send has been accessed the maximum amount of times." + }, "hideTextByDefault": { "message": "기본적으로 텍스트 숨기기" }, @@ -2963,14 +3021,14 @@ "copyCustomFieldNameNotUnique": { "message": "고유 식별자를 찾을 수 없습니다." }, - "convertOrganizationEncryptionDesc": { - "message": "$ORGANIZATION$ 조직은 자체 호스팅 키 서버로 SSO를 사용하고 있습니다 이 조직의 멤버들은 로그인할 때에 마스터 비밀번호를 필요로 하지 않습니다.", - "placeholders": { - "organization": { - "content": "$1", - "example": "My Org Name" - } - } + "removeMasterPasswordForOrganizationUserKeyConnector": { + "message": "A master password is no longer required for members of the following organization. Please confirm the domain below with your organization administrator." + }, + "organizationName": { + "message": "Organization name" + }, + "keyConnectorDomain": { + "message": "Key Connector domain" }, "leaveOrganization": { "message": "조직 나가기" @@ -3532,12 +3590,36 @@ "deviceTrusted": { "message": "신뢰할 수 있는 장치" }, - "sendsNoItemsTitle": { - "message": "활성화된 Send없음", + "trustOrganization": { + "message": "Trust organization" + }, + "trust": { + "message": "Trust" + }, + "doNotTrust": { + "message": "Do not trust" + }, + "organizationNotTrusted": { + "message": "Organization is not trusted" + }, + "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" + }, + "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." + }, + "orgTrustWarning1": { + "message": "This organization has an Enterprise policy that will enroll you in account recovery. Enrollment will allow organization administrators to change your password. Only proceed if you recognize this organization and the fingerprint phrase displayed below matches the organization's fingerprint." + }, + "trustUser": { + "message": "Trust user" + }, + "sendsTitleNoItems": { + "message": "Send sensitive information safely", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, - "sendsNoItemsMessage": { - "message": "Send를 사용하여 암호화된 정보를 어느 사람과도 안전하게 공유합니다.", + "sendsBodyNoItems": { + "message": "Share files and data securely with anyone, on any platform. Your information will remain end-to-end encrypted while limiting exposure.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "inputRequired": { @@ -4456,6 +4538,33 @@ } } }, + "downloadBitwarden": { + "message": "Download Bitwarden" + }, + "downloadBitwardenOnAllDevices": { + "message": "Download Bitwarden on all devices" + }, + "getTheMobileApp": { + "message": "Get the mobile app" + }, + "getTheMobileAppDesc": { + "message": "Access your passwords on the go with the Bitwarden mobile app." + }, + "getTheDesktopApp": { + "message": "Get the desktop app" + }, + "getTheDesktopAppDesc": { + "message": "Access your vault without a browser, then set up unlock with biometrics to expedite unlocking in both the desktop app and browser extension." + }, + "downloadFromBitwardenNow": { + "message": "Download from bitwarden.com now" + }, + "getItOnGooglePlay": { + "message": "Get it on Google Play" + }, + "downloadOnTheAppStore": { + "message": "Download on the App Store" + }, "permanentlyDeleteAttachmentConfirmation": { "message": "정말로 이 첨부파일을 영구적으로 삭제하시겠습니까?" }, @@ -4917,6 +5026,15 @@ "biometricsStatusHelptextUnavailableReasonUnknown": { "message": "Biometric unlock is currently unavailable for an unknown reason." }, + "unlockVault": { + "message": "Unlock your vault in seconds" + }, + "unlockVaultDesc": { + "message": "You can customize your unlock and timeout settings to more quickly access your vault." + }, + "unlockPinSet": { + "message": "Unlock PIN set" + }, "authenticating": { "message": "인증 중" }, @@ -4928,8 +5046,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": { @@ -5079,42 +5197,6 @@ "beta": { "message": "베타" }, - "importantNotice": { - "message": "Important notice" - }, - "setupTwoStepLogin": { - "message": "Set up two-step login" - }, - "newDeviceVerificationNoticeContentPage1": { - "message": "Bitwarden will send a code to your account email to verify logins from new devices starting in February 2025." - }, - "newDeviceVerificationNoticeContentPage2": { - "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" - }, - "newDeviceVerificationNoticePageOneFormContent": { - "message": "Do you have reliable access to your email, $EMAIL$?", - "placeholders": { - "email": { - "content": "$1", - "example": "your_name@email.com" - } - } - }, - "newDeviceVerificationNoticePageOneEmailAccessNo": { - "message": "No, I do not" - }, - "newDeviceVerificationNoticePageOneEmailAccessYes": { - "message": "Yes, I can reliably access my email" - }, - "turnOnTwoStepLogin": { - "message": "Turn on two-step login" - }, - "changeAcctEmail": { - "message": "Change account email" - }, "extensionWidth": { "message": "확장 폭" }, @@ -5169,6 +5251,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 +5283,98 @@ }, "secureDevicesBody": { "message": "Save unlimited passwords across unlimited devices with Bitwarden mobile, browser, and desktop apps." + }, + "nudgeBadgeAria": { + "message": "1 notification" + }, + "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!" + }, + "hasItemsVaultNudgeBodyOne": { + "message": "Autofill items for the current page" + }, + "hasItemsVaultNudgeBodyTwo": { + "message": "Favorite items for easy access" + }, + "hasItemsVaultNudgeBodyThree": { + "message": "Search your vault for something else" + }, + "newLoginNudgeTitle": { + "message": "Save time with autofill" + }, + "newLoginNudgeBodyOne": { + "message": "Include a", + "description": "This is in multiple parts to allow for bold text in the middle of the sentence.", + "example": "Include a Website so this login appears as an autofill suggestion." + }, + "newLoginNudgeBodyBold": { + "message": "Website", + "description": "This is in multiple parts to allow for bold text in the middle of the sentence.", + "example": "Include a Website so this login appears as an autofill suggestion." + }, + "newLoginNudgeBodyTwo": { + "message": "so this login appears as an autofill suggestion.", + "description": "This is in multiple parts to allow for bold text in the middle of the sentence.", + "example": "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" + }, + "newSshNudgeBodyOne": { + "message": "Store your keys and connect with the SSH agent for fast, encrypted authentication.", + "description": "Two part message", + "example": "Store your keys and connect with the SSH agent for fast, encrypted authentication. Learn more about SSH agent" + }, + "newSshNudgeBodyTwo": { + "message": "Learn more about SSH agent", + "description": "Two part message", + "example": "Store your keys and connect with the SSH agent for fast, encrypted authentication. Learn more about SSH agent" + }, + "generatorNudgeTitle": { + "message": "Quickly create passwords" + }, + "generatorNudgeBodyOne": { + "message": "Easily create strong and unique passwords by clicking on", + "description": "Two part message", + "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." + }, + "generatorNudgeBodyTwo": { + "message": "to help you keep your logins secure.", + "description": "Two part message", + "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." + }, + "generatorNudgeBodyAria": { + "message": "Easily create strong and unique passwords by clicking on the Generate password button to help you keep your logins secure.", + "description": "Aria label for the body content of the generator nudge" + }, + "noPermissionsViewPage": { + "message": "You do not have permissions to view this page. Try logging in with a different account." } } diff --git a/apps/browser/src/_locales/lt/messages.json b/apps/browser/src/_locales/lt/messages.json index 2245fdb0029..0884081c173 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 logotipas" + }, "extName": { "message": "„Bitwarden“ slaptažodžių tvarkyklė", "description": "Extension name, MUST be less than 40 characters (Safari restriction)" @@ -20,16 +23,16 @@ "message": "Sukurti paskyrą" }, "newToBitwarden": { - "message": "New to Bitwarden?" + "message": "Pirmą kartą Bitwarden?" }, "logInWithPasskey": { - "message": "Log in with passkey" + "message": "Prisijungti naudojant prieigos raktą" }, "useSingleSignOn": { "message": "Use single sign-on" }, "welcomeBack": { - "message": "Welcome back" + "message": "Sveiki sugrįžę" }, "setAStrongPassword": { "message": "Nustatyti stiprų slaptažodį" @@ -81,7 +84,7 @@ "message": "Pagrindinio slaptažodžio užuomina (neprivaloma)" }, "passwordStrengthScore": { - "message": "Password strength score $SCORE$", + "message": "Slaptažodžio stiprumas $SCORE$", "placeholders": { "score": { "content": "$1", @@ -90,10 +93,10 @@ } }, "joinOrganization": { - "message": "Join organization" + "message": "Prisijungti prie organizacijos" }, "joinOrganizationName": { - "message": "Join $ORGANIZATIONNAME$", + "message": "Prisijungti prie $ORGANIZATIONNAME$", "placeholders": { "organizationName": { "content": "$1", @@ -102,7 +105,7 @@ } }, "finishJoiningThisOrganizationBySettingAMasterPassword": { - "message": "Finish joining this organization by setting a master password." + "message": "Baigėte prisijungimą prie organizacijos nustatant pagrindinį slaptažodį." }, "tab": { "message": "Skirtukas" @@ -129,7 +132,7 @@ "message": "Kopijuoti slaptažodį" }, "copyPassphrase": { - "message": "Copy passphrase" + "message": "Kopijuoti slaptažodžio frazę" }, "copyNote": { "message": "Kopijuoti pastabą" @@ -147,31 +150,31 @@ "message": "Kopijuoti saugos kodą" }, "copyName": { - "message": "Copy name" + "message": "Kopijuoti vardą" }, "copyCompany": { - "message": "Copy company" + "message": "Kopijuoti įmonę" }, "copySSN": { - "message": "Copy Social Security number" + "message": "Kopijuoti socialinės apsaugos numerį" }, "copyPassportNumber": { - "message": "Copy passport number" + "message": "Kopijuoti paso numerį" }, "copyLicenseNumber": { - "message": "Copy license number" + "message": "Kopijuoti licenzijos numerį" }, "copyPrivateKey": { - "message": "Copy private key" + "message": "Kopijuoti privatų raktą" }, "copyPublicKey": { - "message": "Copy public key" + "message": "Kopijuoti viešą raktą" }, "copyFingerprint": { - "message": "Copy fingerprint" + "message": "Kopijuoti piršto antspaudą" }, "copyCustomField": { - "message": "Copy $FIELD$", + "message": "Kopijuoti $FIELD$", "placeholders": { "field": { "content": "$1", @@ -180,13 +183,13 @@ } }, "copyWebsite": { - "message": "Copy website" + "message": "Kopijuoti svetainę" }, "copyNotes": { - "message": "Copy notes" + "message": "Kopijuoti pastabas" }, "copy": { - "message": "Copy", + "message": "Kopijuoti", "description": "Copy to clipboard" }, "fill": { @@ -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,55 @@ "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.", + "notificationNewItemAria": { + "message": "New Item, opens in new window", + "description": "Aria label for the new item button in notification bar confirmation message when error is prompted" + }, + "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": { - "content": "$1" + "label": { + "content": "$1", + "example": "Login" } - }, - "description": "Shown to user after login is updated." + } + }, + "notificationLoginSaveConfirmation": { + "message": "saved to Bitwarden.", + "description": "Shown to user after item is saved." + }, + "notificationLoginUpdatedConfirmation": { + "message": "updated in Bitwarden.", + "description": "Shown to user after item is updated." + }, + "selectItemAriaLabel": { + "message": "Select $ITEMTYPE$, $ITEMNAME$", + "description": "Used by screen readers. $1 is the item type (like vault or folder), $2 is the selected item name.", + "placeholders": { + "itemType": { + "content": "$1" + }, + "itemName": { + "content": "$2" + } + } }, "saveAsNewLoginAction": { "message": "Save as new login", @@ -1082,12 +1120,16 @@ "message": "Update login", "description": "Button text for updating an existing login entry." }, - "saveLoginPrompt": { - "message": "Save login?", + "unlockToSave": { + "message": "Unlock to save this login", + "description": "User prompt to take action in order to save the login they just entered." + }, + "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": { @@ -1323,8 +1365,8 @@ "featureUnavailable": { "message": "Funkcija neprieinama" }, - "encryptionKeyMigrationRequired": { - "message": "Reikalinga šifravimo rakto migracija. Prisijunkite per žiniatinklio saugyklą, kad atnaujintumėte šifravimo raktą." + "legacyEncryptionUnsupported": { + "message": "Legacy encryption is no longer supported. Please contact support to recover your account." }, "premiumMembership": { "message": "Premium narystė" @@ -1557,6 +1599,24 @@ "autofillSuggestionsSectionTitle": { "message": "Autofill suggestions" }, + "autofillSpotlightTitle": { + "message": "Easily find autofill suggestions" + }, + "autofillSpotlightDesc": { + "message": "Turn off your browser's autofill settings, so they don't conflict with Bitwarden." + }, + "turnOffBrowserAutofill": { + "message": "Turn off $BROWSER$ autofill", + "placeholders": { + "browser": { + "content": "$1", + "example": "Chrome" + } + } + }, + "turnOffAutofill": { + "message": "Turn off autofill" + }, "showInlineMenuLabel": { "message": "Show autofill suggestions on form fields" }, @@ -2144,6 +2204,9 @@ "useThisPassword": { "message": "Use this password" }, + "useThisPassphrase": { + "message": "Use this passphrase" + }, "useThisUsername": { "message": "Use this username" }, @@ -2167,15 +2230,6 @@ "vaultTimeoutAction1": { "message": "Timeout action" }, - "newCustomizationOptionsCalloutTitle": { - "message": "New customization options" - }, - "newCustomizationOptionsCalloutContent": { - "message": "Customize your vault experience with quick copy actions, compact mode, and more!" - }, - "newCustomizationOptionsCalloutLink": { - "message": "View all Appearance settings" - }, "lock": { "message": "Užrakinti", "description": "Verb form: to make secure or inaccessible by" @@ -2623,6 +2677,10 @@ "message": "Visi siuntimai", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, + "maxAccessCountReached": { + "message": "Max access count reached", + "description": "This text will be displayed after a Send has been accessed the maximum amount of times." + }, "hideTextByDefault": { "message": "Hide text by default" }, @@ -2963,14 +3021,14 @@ "copyCustomFieldNameNotUnique": { "message": "Unikalus identifikatorius nerastas." }, - "convertOrganizationEncryptionDesc": { - "message": "$ORGANIZATION$ naudoja SSO su savarankiškai sukurtu raktų serveriu. Pagrindinis slaptažodis nebėra reikalingas norint šios organizacijos nariams prisijungti.", - "placeholders": { - "organization": { - "content": "$1", - "example": "My Org Name" - } - } + "removeMasterPasswordForOrganizationUserKeyConnector": { + "message": "A master password is no longer required for members of the following organization. Please confirm the domain below with your organization administrator." + }, + "organizationName": { + "message": "Organization name" + }, + "keyConnectorDomain": { + "message": "Key Connector domain" }, "leaveOrganization": { "message": "Palikti organizaciją" @@ -3532,12 +3590,36 @@ "deviceTrusted": { "message": "Patikimas įrenginys" }, - "sendsNoItemsTitle": { - "message": "Nėra aktyvų „Sends“", + "trustOrganization": { + "message": "Trust organization" + }, + "trust": { + "message": "Trust" + }, + "doNotTrust": { + "message": "Do not trust" + }, + "organizationNotTrusted": { + "message": "Organization is not trusted" + }, + "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" + }, + "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." + }, + "orgTrustWarning1": { + "message": "This organization has an Enterprise policy that will enroll you in account recovery. Enrollment will allow organization administrators to change your password. Only proceed if you recognize this organization and the fingerprint phrase displayed below matches the organization's fingerprint." + }, + "trustUser": { + "message": "Trust user" + }, + "sendsTitleNoItems": { + "message": "Send sensitive information safely", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, - "sendsNoItemsMessage": { - "message": "Naudokite „Send“, kad saugiai bendrintumėte užšifruotą informaciją su bet kuriuo asmeniu.", + "sendsBodyNoItems": { + "message": "Share files and data securely with anyone, on any platform. Your information will remain end-to-end encrypted while limiting exposure.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "inputRequired": { @@ -4456,6 +4538,33 @@ } } }, + "downloadBitwarden": { + "message": "Download Bitwarden" + }, + "downloadBitwardenOnAllDevices": { + "message": "Download Bitwarden on all devices" + }, + "getTheMobileApp": { + "message": "Get the mobile app" + }, + "getTheMobileAppDesc": { + "message": "Access your passwords on the go with the Bitwarden mobile app." + }, + "getTheDesktopApp": { + "message": "Get the desktop app" + }, + "getTheDesktopAppDesc": { + "message": "Access your vault without a browser, then set up unlock with biometrics to expedite unlocking in both the desktop app and browser extension." + }, + "downloadFromBitwardenNow": { + "message": "Download from bitwarden.com now" + }, + "getItOnGooglePlay": { + "message": "Get it on Google Play" + }, + "downloadOnTheAppStore": { + "message": "Download on the App Store" + }, "permanentlyDeleteAttachmentConfirmation": { "message": "Ar tikrai norite negrįžtamai ištrinti šį priedą?" }, @@ -4917,6 +5026,15 @@ "biometricsStatusHelptextUnavailableReasonUnknown": { "message": "Biometric unlock is currently unavailable for an unknown reason." }, + "unlockVault": { + "message": "Unlock your vault in seconds" + }, + "unlockVaultDesc": { + "message": "You can customize your unlock and timeout settings to more quickly access your vault." + }, + "unlockPinSet": { + "message": "Unlock PIN set" + }, "authenticating": { "message": "Authenticating" }, @@ -4928,8 +5046,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": { @@ -5079,42 +5197,6 @@ "beta": { "message": "Beta" }, - "importantNotice": { - "message": "Important notice" - }, - "setupTwoStepLogin": { - "message": "Set up two-step login" - }, - "newDeviceVerificationNoticeContentPage1": { - "message": "Bitwarden will send a code to your account email to verify logins from new devices starting in February 2025." - }, - "newDeviceVerificationNoticeContentPage2": { - "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" - }, - "newDeviceVerificationNoticePageOneFormContent": { - "message": "Do you have reliable access to your email, $EMAIL$?", - "placeholders": { - "email": { - "content": "$1", - "example": "your_name@email.com" - } - } - }, - "newDeviceVerificationNoticePageOneEmailAccessNo": { - "message": "No, I do not" - }, - "newDeviceVerificationNoticePageOneEmailAccessYes": { - "message": "Yes, I can reliably access my email" - }, - "turnOnTwoStepLogin": { - "message": "Turn on two-step login" - }, - "changeAcctEmail": { - "message": "Change account email" - }, "extensionWidth": { "message": "Extension width" }, @@ -5169,6 +5251,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 +5283,98 @@ }, "secureDevicesBody": { "message": "Save unlimited passwords across unlimited devices with Bitwarden mobile, browser, and desktop apps." + }, + "nudgeBadgeAria": { + "message": "1 notification" + }, + "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!" + }, + "hasItemsVaultNudgeBodyOne": { + "message": "Autofill items for the current page" + }, + "hasItemsVaultNudgeBodyTwo": { + "message": "Favorite items for easy access" + }, + "hasItemsVaultNudgeBodyThree": { + "message": "Search your vault for something else" + }, + "newLoginNudgeTitle": { + "message": "Save time with autofill" + }, + "newLoginNudgeBodyOne": { + "message": "Include a", + "description": "This is in multiple parts to allow for bold text in the middle of the sentence.", + "example": "Include a Website so this login appears as an autofill suggestion." + }, + "newLoginNudgeBodyBold": { + "message": "Website", + "description": "This is in multiple parts to allow for bold text in the middle of the sentence.", + "example": "Include a Website so this login appears as an autofill suggestion." + }, + "newLoginNudgeBodyTwo": { + "message": "so this login appears as an autofill suggestion.", + "description": "This is in multiple parts to allow for bold text in the middle of the sentence.", + "example": "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" + }, + "newSshNudgeBodyOne": { + "message": "Store your keys and connect with the SSH agent for fast, encrypted authentication.", + "description": "Two part message", + "example": "Store your keys and connect with the SSH agent for fast, encrypted authentication. Learn more about SSH agent" + }, + "newSshNudgeBodyTwo": { + "message": "Learn more about SSH agent", + "description": "Two part message", + "example": "Store your keys and connect with the SSH agent for fast, encrypted authentication. Learn more about SSH agent" + }, + "generatorNudgeTitle": { + "message": "Quickly create passwords" + }, + "generatorNudgeBodyOne": { + "message": "Easily create strong and unique passwords by clicking on", + "description": "Two part message", + "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." + }, + "generatorNudgeBodyTwo": { + "message": "to help you keep your logins secure.", + "description": "Two part message", + "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." + }, + "generatorNudgeBodyAria": { + "message": "Easily create strong and unique passwords by clicking on the Generate password button to help you keep your logins secure.", + "description": "Aria label for the body content of the generator nudge" + }, + "noPermissionsViewPage": { + "message": "You do not have permissions to view this page. Try logging in with a different account." } } diff --git a/apps/browser/src/_locales/lv/messages.json b/apps/browser/src/_locales/lv/messages.json index 517dacfb72b..63b2098fe00 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)" @@ -270,7 +273,7 @@ "message": "Turpināt" }, "sendVerificationCode": { - "message": "Sūtīt apstiprinājuma kodu uz e-pastu" + "message": "Nosūtīt apliecinājuma kodu e-pastā" }, "sendCode": { "message": "Nosūtīt kodu" @@ -279,7 +282,7 @@ "message": "Kods nosūtīts" }, "verificationCode": { - "message": "Apstiprināšanas kods" + "message": "Apliecinājuma kods" }, "confirmIdentity": { "message": "Jāapliecina sava identitāte, lai turpinātu." @@ -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" @@ -808,13 +811,13 @@ "message": "Mēs nosūtījām galvenās paroles norādi e-pastā." }, "verificationCodeRequired": { - "message": "Apstiprinājuma kods ir nepieciešams." + "message": "Apliecinājuma kods ir nepieciešams." }, "webauthnCancelOrTimeout": { "message": "Autentifikācija tika atcelta vai tā aizņēma pārāk daudz laika. Lūgums mēģināt vēlreiz." }, "invalidVerificationCode": { - "message": "Nederīgs apstiprinājuma kods" + "message": "Nederīgs apliecinājuma kods" }, "valueCopied": { "message": "$VALUE$ ir starpliktuvē", @@ -842,10 +845,10 @@ "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ā." + "message": "Bitwarden var glabāt un aizpildīt divpakāpju apliecinājuma 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ā." + "message": "Bitwarden var glabāt un aizpildīt divpakāpju apliecinājuma 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ā." }, "learnMoreAboutAuthenticators": { "message": "Uzzināt vairāk par autentificētājiem" @@ -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,55 @@ "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.", + "notificationNewItemAria": { + "message": "Jauns vienums, atvērsies jaunā logā", + "description": "Aria label for the new item button in notification bar confirmation message when error is prompted" + }, + "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": { - "content": "$1" + "label": { + "content": "$1", + "example": "Login" } - }, - "description": "Shown to user after login is updated." + } + }, + "notificationLoginSaveConfirmation": { + "message": "saglabāts Bitwarden.", + "description": "Shown to user after item is saved." + }, + "notificationLoginUpdatedConfirmation": { + "message": "atjaunināts Bitwarden.", + "description": "Shown to user after item is updated." + }, + "selectItemAriaLabel": { + "message": "Atlasīt $ITEMTYPE$, $ITEMNAME$", + "description": "Used by screen readers. $1 is the item type (like vault or folder), $2 is the selected item name.", + "placeholders": { + "itemType": { + "content": "$1" + }, + "itemName": { + "content": "$2" + } + } }, "saveAsNewLoginAction": { "message": "Saglabāt kā jaunu pieteikšanās vienumu", @@ -1082,12 +1120,16 @@ "message": "Atjaunināt pieteikšanās vienumu", "description": "Button text for updating an existing login entry." }, - "saveLoginPrompt": { - "message": "Saglabāt pieteikšanās vienumu?", + "unlockToSave": { + "message": "Jāatslēdz, lai saglabātu šo pieteikšanās vienumu", + "description": "User prompt to take action in order to save the login they just entered." + }, + "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": { @@ -1282,10 +1324,10 @@ "message": "Autentificētāja atslēga (TOTP)" }, "verificationCodeTotp": { - "message": "Apstiprinājuma kods (TOTP)" + "message": "Apliecinājuma kods (TOTP)" }, "copyVerificationCode": { - "message": "Ievietot apstiprinājuma kodu starpliktuvē" + "message": "Ievietot apliecinājuma kodu starpliktuvē" }, "attachments": { "message": "Pielikumi" @@ -1323,8 +1365,8 @@ "featureUnavailable": { "message": "Iespēja nav pieejama" }, - "encryptionKeyMigrationRequired": { - "message": "Nepieciešama šifrēšanas atslēgas nomaiņa. Lūgums pieteikties tīmekļa glabātavā, lai atjauninātu savu šifrēšanas atslēgu." + "legacyEncryptionUnsupported": { + "message": "Mantota šifrēšana vairs netiek atbalstīta. Lūgums sazināties ar atbalstu, lai atkoptu savu kontu." }, "premiumMembership": { "message": "Premium dalība" @@ -1357,7 +1399,7 @@ "message": "Paroļu higiēnas, konta veselības un datu noplūžu pārskati, lai uzturētu glabātavu drošu." }, "ppremiumSignUpTotp": { - "message": "TOTP apstiprinājuma koda (2FA) veidotājs glabātavas pieteikšanās vienumiem." + "message": "TOTP apliecinājuma koda (2FA) veidotājs glabātavas pieteikšanās vienumiem." }, "ppremiumSignUpSupport": { "message": "Priekšrocīgs lietotāju atbalsts." @@ -1405,7 +1447,7 @@ "message": "Automātiski ievietot TOTP starpliktuvē" }, "disableAutoTotpCopyDesc": { - "message": "Ja pieteikšanās vienumam ir pievienota autentificētāja atslēga, TOTP apstiprinājuma kods tiks automātiski ievietots starpliktuvē, kad vien tiks automātiski aizpildīta pieteikšanās veidne." + "message": "Ja pieteikšanās vienumam ir pievienota autentificētāja atslēga, TOTP apliecinājuma kods tiks automātiski ievietots starpliktuvē, kad vien tiks automātiski aizpildīta pieteikšanās veidne." }, "enableAutoBiometricsPrompt": { "message": "Palaižot vaicāt biometriju" @@ -1423,7 +1465,7 @@ "message": "Iestājās autentificēšanās sesijas noildze. Lūgums sākt pieteikšanos no jauna." }, "verificationCodeEmailSent": { - "message": "E-pasts apstiprināšanai nosūtīts uz $EMAIL$.", + "message": "Apliecinājuma e-pasta ziņojums nosūtīts uz $EMAIL$.", "placeholders": { "email": { "content": "$1", @@ -1511,7 +1553,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" @@ -1557,6 +1599,24 @@ "autofillSuggestionsSectionTitle": { "message": "Automātiskās aizpildes ieteikumi" }, + "autofillSpotlightTitle": { + "message": "Viegla automātiskās aizpildes ieteikumu atrašana" + }, + "autofillSpotlightDesc": { + "message": "Jāizslēdz sava pārlūka automātiskās aizpildes iestatījumi, lai tie nebūtu pretrunā ar Bitwarden." + }, + "turnOffBrowserAutofill": { + "message": "Izslēgt $BROWSER$ automātisko aizpildi", + "placeholders": { + "browser": { + "content": "$1", + "example": "Chrome" + } + } + }, + "turnOffAutofill": { + "message": "Izslēgt automātisko aizpildi" + }, "showInlineMenuLabel": { "message": "Rādīt automātiskās aizpildes ieteikumuis veidlapu laukos" }, @@ -1686,7 +1746,7 @@ "description": "This describes a value that is 'linked' (tied) to another value." }, "popup2faCloseMessage": { - "message": "Klikšķināšana ārpus uznirstošā loga, lai e-pastā apskatītu apstiprinājuma kodu, to aizvērs. Vai atvērt to atsevišķā logā, lai tas netiktu aizvērts?" + "message": "Klikšķināšana ārpus uznirstošā loga, lai e-pastā apskatītu apliecinājuma kodu, to aizvērs. Vai atvērt to atsevišķā logā, lai tas netiktu aizvērts?" }, "popupU2fCloseMessage": { "message": "Šis pārlūks nevar apstrādāt U2F pieprasījumus šajā uznirstošajā logā. Vai atvērt to atsevišķā logā, lai varētu pieteikties, izmantojot U2F?" @@ -1976,7 +2036,7 @@ "description": "A URL's host value. For example, the host of https://sub.domain.com:443 is 'sub.domain.com:443'." }, "exact": { - "message": "Tiešs" + "message": "Tieši" }, "startsWith": { "message": "Sākas ar" @@ -2144,6 +2204,9 @@ "useThisPassword": { "message": "Izmantot šo paroli" }, + "useThisPassphrase": { + "message": "Izmantot šo paroles vārdkopu" + }, "useThisUsername": { "message": "Izmantot šo lietotājvārdu" }, @@ -2167,15 +2230,6 @@ "vaultTimeoutAction1": { "message": "Noildzes darbība" }, - "newCustomizationOptionsCalloutTitle": { - "message": "Jaunas pielāgošanas iespējas" - }, - "newCustomizationOptionsCalloutContent": { - "message": "Savu glabātavas pieredzi var pielāgot ar ātrām kopēšanas darbībām, ciešo izkārtojumu un vēl." - }, - "newCustomizationOptionsCalloutLink": { - "message": "Apskatīt visus izskata iestatījumus" - }, "lock": { "message": "Slēgt", "description": "Verb form: to make secure or inaccessible by" @@ -2338,7 +2392,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:" @@ -2623,6 +2677,10 @@ "message": "Visi Send", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, + "maxAccessCountReached": { + "message": "Sasniegts lielākais pieļaujamais piekļuves reižu skaits", + "description": "This text will be displayed after a Send has been accessed the maximum amount of times." + }, "hideTextByDefault": { "message": "Pēc noklusējuma paslēpt tekstu" }, @@ -2817,13 +2875,13 @@ "message": "Šī darbība ir aizsargāta. Lai turpinātu, ir jāievada galvenā parole, lai apliecinātu savu identitāti." }, "emailVerificationRequired": { - "message": "Nepieciešama e-pasta adreses apstiprināšana" + "message": "Nepieciešama e-pasta adreses apliecināšana" }, "emailVerifiedV2": { "message": "E-pasta adrese ir apliecināta" }, "emailVerificationRequiredDesc": { - "message": "Ir nepieciešams apstiprināt e-pasta adresi, lai būtu iespējams izmantot šo iespēju. To var izdarīt tīmekļa glabātavā." + "message": "Ir nepieciešams apliecināt savu e-pasta adresi, lai būtu iespējams izmantot šo iespēju. To var izdarīt tīmekļa glabātavā." }, "updatedMasterPassword": { "message": "Galvenā parole atjaunināta" @@ -2963,14 +3021,14 @@ "copyCustomFieldNameNotUnique": { "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.", - "placeholders": { - "organization": { - "content": "$1", - "example": "My Org Name" - } - } + "removeMasterPasswordForOrganizationUserKeyConnector": { + "message": "Galvenā parole vairs nav nepieciešama turpmāk minētās apvienības dalībniekiem. Lūgums saskaņot zemāk esošo domēnu ar savas apvienības pārvaldītāju." + }, + "organizationName": { + "message": "Apvienības nosaukums" + }, + "keyConnectorDomain": { + "message": "Key Connector domēns" }, "leaveOrganization": { "message": "Pamest apvienību" @@ -3283,7 +3341,7 @@ "message": "Servera versija" }, "selfHostedServer": { - "message": "pašizvietots" + "message": "pašmitināts" }, "thirdParty": { "message": "Trešās puses" @@ -3532,12 +3590,36 @@ "deviceTrusted": { "message": "Ierīce ir uzticama" }, - "sendsNoItemsTitle": { - "message": "Nav spēkā esošu Send", + "trustOrganization": { + "message": "Uzticēties apvienībai" + }, + "trust": { + "message": "Uzticēties" + }, + "doNotTrust": { + "message": "Neuzticēties" + }, + "organizationNotTrusted": { + "message": "Apvienība nav uzticama" + }, + "emergencyAccessTrustWarning": { + "message": "Lai nodrošinātu sava konta drošību, jāapstiprina tikai tad, ja šim lietotājam ir nodrošināta ārkārtas piekļuve un tā pirkstu nospiedums atbilsta tam, kas ir attēlots tā kontā" + }, + "orgTrustWarning": { + "message": "Lai nodrošinātu sava konta drošību, jāturpina tikai tad, ja esi šīs apvienības dalībnieks, ir iespējota konta atkope un zemāk attēlotais pirkstu nospiedums atbilst apvienības pirkstu nospiedumam." + }, + "orgTrustWarning1": { + "message": "Šai apvienībai ir uzņēmējdarbības pamatnostādne, kas Tevi iekļaus konta atkopē. Iekļaušana ļaus apvienības pārvaldītājiem nomainīt Tavu paroli. Turpini tikai tad, ja zini šo apvienību un zemāk attēlotā pirkstu nospieduma vārdkopa atbilst apvienības pirkstu nospiedumam!" + }, + "trustUser": { + "message": "Uzticēties lietotājam" + }, + "sendsTitleNoItems": { + "message": "Drošā veidā nosūti jūtīgu informāciju", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, - "sendsNoItemsMessage": { - "message": "Send ir izmantojams, lai ar ikvienu droši kopīgotu šifrētu informāciju.", + "sendsBodyNoItems": { + "message": "Drošā veidā kopīgo datnes un datus ar ikvienu jebkurā platformā! Tava informācija paliks pilnībā šifrēta, vienlaikus ierobežojot riskantumu.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "inputRequired": { @@ -3706,7 +3788,7 @@ "description": "Screen reader text (aria-label) for unlock account button in overlay" }, "totpCodeAria": { - "message": "Laikā balstīts vienreizējas izmantošanas paroles apliecināšanas kods", + "message": "Laikā balstīts vienreizējas izmantošanas paroles apliecinājuma kods", "description": "Aria label for the totp code displayed in the inline menu for autofill" }, "totpSecondsSpanAria": { @@ -3799,7 +3881,7 @@ "message": "Jāmēģina vēlreiz" }, "verificationRequiredForActionSetPinToContinue": { - "message": "Šai darbībai ir nepieciešama apliecināšana. Jāiestata PIN, lai turpinātu." + "message": "Šai darbībai ir nepieciešama apliecinājums. Jāiestata PIN, lai turpinātu." }, "setPin": { "message": "Iestatīt PIN" @@ -3945,7 +4027,7 @@ "message": "Piekļuves atslēga netiks ievietota klonētajā vienumā. Vai turpināt šī vienuma klonēšanu?" }, "passkeyFeatureIsNotImplementedForAccountsWithoutMasterPassword": { - "message": "Vietne, kurā tika uzsākta darbība, pieprasa pārbaudi. Šī iespēja vēl nav īstenota kontiem, kuriem nav galvenās paroles." + "message": "Vietne, kurā tika uzsākta darbība, pieprasa apliecinājumu. Šī iespēja vēl nav īstenota kontiem, kuriem nav galvenās paroles." }, "logInWithPasskeyQuestion": { "message": "Pieteikties ar piekļuves atslēgu?" @@ -4456,6 +4538,33 @@ } } }, + "downloadBitwarden": { + "message": "Lejupielādē Bitwarden" + }, + "downloadBitwardenOnAllDevices": { + "message": "Lejupielādē Bitwarden visās ierīcēs" + }, + "getTheMobileApp": { + "message": "Iegūsti viedierīču lietotni" + }, + "getTheMobileAppDesc": { + "message": "Piekļūsti savām parolēm kustībā ar Bitwarden viedierīču lietotni!" + }, + "getTheDesktopApp": { + "message": "Iegūsti darbvirsmas lietotni" + }, + "getTheDesktopAppDesc": { + "message": "Piekļūsti savai glabātavai bez pārlūka, tad iestati atslēgšanu ar biometriju, lai paātrinātu atslēgšanu gan darbvirsmas lietotnē, gan pārlūka paplašinājumā!" + }, + "downloadFromBitwardenNow": { + "message": "Lejupielādē no bitwarden.com tagad" + }, + "getItOnGooglePlay": { + "message": "Iegūt to Google Play" + }, + "downloadOnTheAppStore": { + "message": "Lejupielādēt no App Store" + }, "permanentlyDeleteAttachmentConfirmation": { "message": "Vai tiešām neatgriezeniski izdzēst šo pielikumu?" }, @@ -4917,6 +5026,15 @@ "biometricsStatusHelptextUnavailableReasonUnknown": { "message": "Atslēgšana ar biometriju pašlaik nav pieejama nezināma iemesla dēļ." }, + "unlockVault": { + "message": "Atslēdz savu glabātavu dažās sekundēs" + }, + "unlockVaultDesc": { + "message": "Tu vari pielāgot savus atslēgšanas un noildzes iestatījumus, lai ātrāk piekļūtu savai glabātavai." + }, + "unlockPinSet": { + "message": "Atslēgšanas PIN iestatīts" + }, "authenticating": { "message": "Autentificē" }, @@ -4928,8 +5046,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": { @@ -5079,42 +5197,6 @@ "beta": { "message": "Beta" }, - "importantNotice": { - "message": "Svarīgs paziņojums" - }, - "setupTwoStepLogin": { - "message": "Iestatīt divpakāpju pieteikšanos" - }, - "newDeviceVerificationNoticeContentPage1": { - "message": "Bitwarden, sākot ar 2025. gada februāri, nosūtīs kodu uz konta e-pasta adresi, lai apliecinātu pieteikšanos no jaunām ierīcēm." - }, - "newDeviceVerificationNoticeContentPage2": { - "message": "Var iestatīt divpakāpju pieteikšanos kā citu veidu, kā aizsargāt savu kontu, vai iestatīt savu e-pasta adresi uz tādu, kurai ir piekļuve." - }, - "remindMeLater": { - "message": "Atgādināt man vēlāk" - }, - "newDeviceVerificationNoticePageOneFormContent": { - "message": "Vai ir uzticama piekļuve savai e-pasta adresei $EMAIL$?", - "placeholders": { - "email": { - "content": "$1", - "example": "your_name@email.com" - } - } - }, - "newDeviceVerificationNoticePageOneEmailAccessNo": { - "message": "Nē, nav" - }, - "newDeviceVerificationNoticePageOneEmailAccessYes": { - "message": "Jā, varu uzticami piekļūt savam e-pastam" - }, - "turnOnTwoStepLogin": { - "message": "Ieslēgt divpakāpju pieteikšanos" - }, - "changeAcctEmail": { - "message": "Mainīt konta e-pasta adresi" - }, "extensionWidth": { "message": "Paplašinājuma platums" }, @@ -5169,6 +5251,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 +5283,98 @@ }, "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." + }, + "nudgeBadgeAria": { + "message": "1 paziņojums" + }, + "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ā!" + }, + "hasItemsVaultNudgeBodyOne": { + "message": "Automātiska pašreizējās lapas vienumu aizpildīšana" + }, + "hasItemsVaultNudgeBodyTwo": { + "message": "Vienumu izlase vieglai piekļuvei" + }, + "hasItemsVaultNudgeBodyThree": { + "message": "Meklē savā glabātavā kaut ko citu" + }, + "newLoginNudgeTitle": { + "message": "Laika ietaupīšana ar automātisko aizpildi" + }, + "newLoginNudgeBodyOne": { + "message": "Iekļauj", + "description": "This is in multiple parts to allow for bold text in the middle of the sentence.", + "example": "Include a Website so this login appears as an autofill suggestion." + }, + "newLoginNudgeBodyBold": { + "message": "tīmekļvietni,", + "description": "This is in multiple parts to allow for bold text in the middle of the sentence.", + "example": "Include a Website so this login appears as an autofill suggestion." + }, + "newLoginNudgeBodyTwo": { + "message": "lai šis pieteikšanās vienums parādītos kā automātiskās aizpildes ieteikums!", + "description": "This is in multiple parts to allow for bold text in the middle of the sentence.", + "example": "Include a Website so this login appears as an autofill suggestion." + }, + "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" + }, + "newSshNudgeBodyOne": { + "message": "Glabā savas atslēgas un savienojies ar SSH aģentu ātrai, šifrētai autentificēšanai!", + "description": "Two part message", + "example": "Store your keys and connect with the SSH agent for fast, encrypted authentication. Learn more about SSH agent" + }, + "newSshNudgeBodyTwo": { + "message": "Uzzināt vairāk par SSH aģentu", + "description": "Two part message", + "example": "Store your keys and connect with the SSH agent for fast, encrypted authentication. Learn more about SSH agent" + }, + "generatorNudgeTitle": { + "message": "Ātra paroļu izveidošana" + }, + "generatorNudgeBodyOne": { + "message": "Vienkārša spēcīgu un neatkārtojamu paroļu izveidošana ar", + "description": "Two part message", + "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." + }, + "generatorNudgeBodyTwo": { + "message": ", lai palīdzētu uzturērt pieteikšanās vienumus drošus.", + "description": "Two part message", + "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." + }, + "generatorNudgeBodyAria": { + "message": "Vienkārša spēcīgu un neatkārtojamu paroļu izveidošana ar pogu \"Izveidot paroli\", lai palīdzētu uzturēt pieteikšanās vienumus drošus.", + "description": "Aria label for the body content of the generator nudge" + }, + "noPermissionsViewPage": { + "message": "Nav atļaujas apskatīt šo lapu. Jāmēģina pieteikties ar citu kontu." } } diff --git a/apps/browser/src/_locales/ml/messages.json b/apps/browser/src/_locales/ml/messages.json index 49a2695ce6f..8a59821fc7a 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,55 @@ "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.", + "notificationNewItemAria": { + "message": "New Item, opens in new window", + "description": "Aria label for the new item button in notification bar confirmation message when error is prompted" + }, + "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": { - "content": "$1" + "label": { + "content": "$1", + "example": "Login" } - }, - "description": "Shown to user after login is updated." + } + }, + "notificationLoginSaveConfirmation": { + "message": "saved to Bitwarden.", + "description": "Shown to user after item is saved." + }, + "notificationLoginUpdatedConfirmation": { + "message": "updated in Bitwarden.", + "description": "Shown to user after item is updated." + }, + "selectItemAriaLabel": { + "message": "Select $ITEMTYPE$, $ITEMNAME$", + "description": "Used by screen readers. $1 is the item type (like vault or folder), $2 is the selected item name.", + "placeholders": { + "itemType": { + "content": "$1" + }, + "itemName": { + "content": "$2" + } + } }, "saveAsNewLoginAction": { "message": "Save as new login", @@ -1082,12 +1120,16 @@ "message": "Update login", "description": "Button text for updating an existing login entry." }, - "saveLoginPrompt": { - "message": "Save login?", + "unlockToSave": { + "message": "Unlock to save this login", + "description": "User prompt to take action in order to save the login they just entered." + }, + "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": { @@ -1323,8 +1365,8 @@ "featureUnavailable": { "message": "സവിശേഷത ലഭ്യമല്ല" }, - "encryptionKeyMigrationRequired": { - "message": "Encryption key migration required. Please login through the web vault to update your encryption key." + "legacyEncryptionUnsupported": { + "message": "Legacy encryption is no longer supported. Please contact support to recover your account." }, "premiumMembership": { "message": "പ്രീമിയം അംഗത്വം" @@ -1557,6 +1599,24 @@ "autofillSuggestionsSectionTitle": { "message": "Autofill suggestions" }, + "autofillSpotlightTitle": { + "message": "Easily find autofill suggestions" + }, + "autofillSpotlightDesc": { + "message": "Turn off your browser's autofill settings, so they don't conflict with Bitwarden." + }, + "turnOffBrowserAutofill": { + "message": "Turn off $BROWSER$ autofill", + "placeholders": { + "browser": { + "content": "$1", + "example": "Chrome" + } + } + }, + "turnOffAutofill": { + "message": "Turn off autofill" + }, "showInlineMenuLabel": { "message": "Show autofill suggestions on form fields" }, @@ -2144,6 +2204,9 @@ "useThisPassword": { "message": "Use this password" }, + "useThisPassphrase": { + "message": "Use this passphrase" + }, "useThisUsername": { "message": "Use this username" }, @@ -2167,15 +2230,6 @@ "vaultTimeoutAction1": { "message": "Timeout action" }, - "newCustomizationOptionsCalloutTitle": { - "message": "New customization options" - }, - "newCustomizationOptionsCalloutContent": { - "message": "Customize your vault experience with quick copy actions, compact mode, and more!" - }, - "newCustomizationOptionsCalloutLink": { - "message": "View all Appearance settings" - }, "lock": { "message": "പൂട്ടുക", "description": "Verb form: to make secure or inaccessible by" @@ -2623,6 +2677,10 @@ "message": "All Sends", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, + "maxAccessCountReached": { + "message": "Max access count reached", + "description": "This text will be displayed after a Send has been accessed the maximum amount of times." + }, "hideTextByDefault": { "message": "Hide text by default" }, @@ -2963,14 +3021,14 @@ "copyCustomFieldNameNotUnique": { "message": "No unique identifier found." }, - "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.", - "placeholders": { - "organization": { - "content": "$1", - "example": "My Org Name" - } - } + "removeMasterPasswordForOrganizationUserKeyConnector": { + "message": "A master password is no longer required for members of the following organization. Please confirm the domain below with your organization administrator." + }, + "organizationName": { + "message": "Organization name" + }, + "keyConnectorDomain": { + "message": "Key Connector domain" }, "leaveOrganization": { "message": "Leave organization" @@ -3532,12 +3590,36 @@ "deviceTrusted": { "message": "Device trusted" }, - "sendsNoItemsTitle": { - "message": "No active Sends", + "trustOrganization": { + "message": "Trust organization" + }, + "trust": { + "message": "Trust" + }, + "doNotTrust": { + "message": "Do not trust" + }, + "organizationNotTrusted": { + "message": "Organization is not trusted" + }, + "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" + }, + "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." + }, + "orgTrustWarning1": { + "message": "This organization has an Enterprise policy that will enroll you in account recovery. Enrollment will allow organization administrators to change your password. Only proceed if you recognize this organization and the fingerprint phrase displayed below matches the organization's fingerprint." + }, + "trustUser": { + "message": "Trust user" + }, + "sendsTitleNoItems": { + "message": "Send sensitive information safely", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, - "sendsNoItemsMessage": { - "message": "Use Send to securely share encrypted information with anyone.", + "sendsBodyNoItems": { + "message": "Share files and data securely with anyone, on any platform. Your information will remain end-to-end encrypted while limiting exposure.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "inputRequired": { @@ -4456,6 +4538,33 @@ } } }, + "downloadBitwarden": { + "message": "Download Bitwarden" + }, + "downloadBitwardenOnAllDevices": { + "message": "Download Bitwarden on all devices" + }, + "getTheMobileApp": { + "message": "Get the mobile app" + }, + "getTheMobileAppDesc": { + "message": "Access your passwords on the go with the Bitwarden mobile app." + }, + "getTheDesktopApp": { + "message": "Get the desktop app" + }, + "getTheDesktopAppDesc": { + "message": "Access your vault without a browser, then set up unlock with biometrics to expedite unlocking in both the desktop app and browser extension." + }, + "downloadFromBitwardenNow": { + "message": "Download from bitwarden.com now" + }, + "getItOnGooglePlay": { + "message": "Get it on Google Play" + }, + "downloadOnTheAppStore": { + "message": "Download on the App Store" + }, "permanentlyDeleteAttachmentConfirmation": { "message": "Are you sure you want to permanently delete this attachment?" }, @@ -4917,6 +5026,15 @@ "biometricsStatusHelptextUnavailableReasonUnknown": { "message": "Biometric unlock is currently unavailable for an unknown reason." }, + "unlockVault": { + "message": "Unlock your vault in seconds" + }, + "unlockVaultDesc": { + "message": "You can customize your unlock and timeout settings to more quickly access your vault." + }, + "unlockPinSet": { + "message": "Unlock PIN set" + }, "authenticating": { "message": "Authenticating" }, @@ -4928,8 +5046,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": { @@ -5079,42 +5197,6 @@ "beta": { "message": "Beta" }, - "importantNotice": { - "message": "Important notice" - }, - "setupTwoStepLogin": { - "message": "Set up two-step login" - }, - "newDeviceVerificationNoticeContentPage1": { - "message": "Bitwarden will send a code to your account email to verify logins from new devices starting in February 2025." - }, - "newDeviceVerificationNoticeContentPage2": { - "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" - }, - "newDeviceVerificationNoticePageOneFormContent": { - "message": "Do you have reliable access to your email, $EMAIL$?", - "placeholders": { - "email": { - "content": "$1", - "example": "your_name@email.com" - } - } - }, - "newDeviceVerificationNoticePageOneEmailAccessNo": { - "message": "No, I do not" - }, - "newDeviceVerificationNoticePageOneEmailAccessYes": { - "message": "Yes, I can reliably access my email" - }, - "turnOnTwoStepLogin": { - "message": "Turn on two-step login" - }, - "changeAcctEmail": { - "message": "Change account email" - }, "extensionWidth": { "message": "Extension width" }, @@ -5169,6 +5251,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 +5283,98 @@ }, "secureDevicesBody": { "message": "Save unlimited passwords across unlimited devices with Bitwarden mobile, browser, and desktop apps." + }, + "nudgeBadgeAria": { + "message": "1 notification" + }, + "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!" + }, + "hasItemsVaultNudgeBodyOne": { + "message": "Autofill items for the current page" + }, + "hasItemsVaultNudgeBodyTwo": { + "message": "Favorite items for easy access" + }, + "hasItemsVaultNudgeBodyThree": { + "message": "Search your vault for something else" + }, + "newLoginNudgeTitle": { + "message": "Save time with autofill" + }, + "newLoginNudgeBodyOne": { + "message": "Include a", + "description": "This is in multiple parts to allow for bold text in the middle of the sentence.", + "example": "Include a Website so this login appears as an autofill suggestion." + }, + "newLoginNudgeBodyBold": { + "message": "Website", + "description": "This is in multiple parts to allow for bold text in the middle of the sentence.", + "example": "Include a Website so this login appears as an autofill suggestion." + }, + "newLoginNudgeBodyTwo": { + "message": "so this login appears as an autofill suggestion.", + "description": "This is in multiple parts to allow for bold text in the middle of the sentence.", + "example": "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" + }, + "newSshNudgeBodyOne": { + "message": "Store your keys and connect with the SSH agent for fast, encrypted authentication.", + "description": "Two part message", + "example": "Store your keys and connect with the SSH agent for fast, encrypted authentication. Learn more about SSH agent" + }, + "newSshNudgeBodyTwo": { + "message": "Learn more about SSH agent", + "description": "Two part message", + "example": "Store your keys and connect with the SSH agent for fast, encrypted authentication. Learn more about SSH agent" + }, + "generatorNudgeTitle": { + "message": "Quickly create passwords" + }, + "generatorNudgeBodyOne": { + "message": "Easily create strong and unique passwords by clicking on", + "description": "Two part message", + "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." + }, + "generatorNudgeBodyTwo": { + "message": "to help you keep your logins secure.", + "description": "Two part message", + "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." + }, + "generatorNudgeBodyAria": { + "message": "Easily create strong and unique passwords by clicking on the Generate password button to help you keep your logins secure.", + "description": "Aria label for the body content of the generator nudge" + }, + "noPermissionsViewPage": { + "message": "You do not have permissions to view this page. Try logging in with a different account." } } diff --git a/apps/browser/src/_locales/mr/messages.json b/apps/browser/src/_locales/mr/messages.json index 6b2f7b8bc32..01d8e475f0b 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,55 @@ "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.", + "notificationNewItemAria": { + "message": "New Item, opens in new window", + "description": "Aria label for the new item button in notification bar confirmation message when error is prompted" + }, + "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": { - "content": "$1" + "label": { + "content": "$1", + "example": "Login" } - }, - "description": "Shown to user after login is updated." + } + }, + "notificationLoginSaveConfirmation": { + "message": "saved to Bitwarden.", + "description": "Shown to user after item is saved." + }, + "notificationLoginUpdatedConfirmation": { + "message": "updated in Bitwarden.", + "description": "Shown to user after item is updated." + }, + "selectItemAriaLabel": { + "message": "Select $ITEMTYPE$, $ITEMNAME$", + "description": "Used by screen readers. $1 is the item type (like vault or folder), $2 is the selected item name.", + "placeholders": { + "itemType": { + "content": "$1" + }, + "itemName": { + "content": "$2" + } + } }, "saveAsNewLoginAction": { "message": "Save as new login", @@ -1082,12 +1120,16 @@ "message": "Update login", "description": "Button text for updating an existing login entry." }, - "saveLoginPrompt": { - "message": "Save login?", + "unlockToSave": { + "message": "Unlock to save this login", + "description": "User prompt to take action in order to save the login they just entered." + }, + "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": { @@ -1323,8 +1365,8 @@ "featureUnavailable": { "message": "Feature unavailable" }, - "encryptionKeyMigrationRequired": { - "message": "Encryption key migration required. Please login through the web vault to update your encryption key." + "legacyEncryptionUnsupported": { + "message": "Legacy encryption is no longer supported. Please contact support to recover your account." }, "premiumMembership": { "message": "Premium membership" @@ -1557,6 +1599,24 @@ "autofillSuggestionsSectionTitle": { "message": "Autofill suggestions" }, + "autofillSpotlightTitle": { + "message": "Easily find autofill suggestions" + }, + "autofillSpotlightDesc": { + "message": "Turn off your browser's autofill settings, so they don't conflict with Bitwarden." + }, + "turnOffBrowserAutofill": { + "message": "Turn off $BROWSER$ autofill", + "placeholders": { + "browser": { + "content": "$1", + "example": "Chrome" + } + } + }, + "turnOffAutofill": { + "message": "Turn off autofill" + }, "showInlineMenuLabel": { "message": "Show autofill suggestions on form fields" }, @@ -2144,6 +2204,9 @@ "useThisPassword": { "message": "Use this password" }, + "useThisPassphrase": { + "message": "Use this passphrase" + }, "useThisUsername": { "message": "Use this username" }, @@ -2167,15 +2230,6 @@ "vaultTimeoutAction1": { "message": "Timeout action" }, - "newCustomizationOptionsCalloutTitle": { - "message": "New customization options" - }, - "newCustomizationOptionsCalloutContent": { - "message": "Customize your vault experience with quick copy actions, compact mode, and more!" - }, - "newCustomizationOptionsCalloutLink": { - "message": "View all Appearance settings" - }, "lock": { "message": "Lock", "description": "Verb form: to make secure or inaccessible by" @@ -2623,6 +2677,10 @@ "message": "All Sends", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, + "maxAccessCountReached": { + "message": "Max access count reached", + "description": "This text will be displayed after a Send has been accessed the maximum amount of times." + }, "hideTextByDefault": { "message": "Hide text by default" }, @@ -2963,14 +3021,14 @@ "copyCustomFieldNameNotUnique": { "message": "No unique identifier found." }, - "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.", - "placeholders": { - "organization": { - "content": "$1", - "example": "My Org Name" - } - } + "removeMasterPasswordForOrganizationUserKeyConnector": { + "message": "A master password is no longer required for members of the following organization. Please confirm the domain below with your organization administrator." + }, + "organizationName": { + "message": "Organization name" + }, + "keyConnectorDomain": { + "message": "Key Connector domain" }, "leaveOrganization": { "message": "Leave organization" @@ -3532,12 +3590,36 @@ "deviceTrusted": { "message": "Device trusted" }, - "sendsNoItemsTitle": { - "message": "No active Sends", + "trustOrganization": { + "message": "Trust organization" + }, + "trust": { + "message": "Trust" + }, + "doNotTrust": { + "message": "Do not trust" + }, + "organizationNotTrusted": { + "message": "Organization is not trusted" + }, + "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" + }, + "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." + }, + "orgTrustWarning1": { + "message": "This organization has an Enterprise policy that will enroll you in account recovery. Enrollment will allow organization administrators to change your password. Only proceed if you recognize this organization and the fingerprint phrase displayed below matches the organization's fingerprint." + }, + "trustUser": { + "message": "Trust user" + }, + "sendsTitleNoItems": { + "message": "Send sensitive information safely", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, - "sendsNoItemsMessage": { - "message": "Use Send to securely share encrypted information with anyone.", + "sendsBodyNoItems": { + "message": "Share files and data securely with anyone, on any platform. Your information will remain end-to-end encrypted while limiting exposure.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "inputRequired": { @@ -4456,6 +4538,33 @@ } } }, + "downloadBitwarden": { + "message": "Download Bitwarden" + }, + "downloadBitwardenOnAllDevices": { + "message": "Download Bitwarden on all devices" + }, + "getTheMobileApp": { + "message": "Get the mobile app" + }, + "getTheMobileAppDesc": { + "message": "Access your passwords on the go with the Bitwarden mobile app." + }, + "getTheDesktopApp": { + "message": "Get the desktop app" + }, + "getTheDesktopAppDesc": { + "message": "Access your vault without a browser, then set up unlock with biometrics to expedite unlocking in both the desktop app and browser extension." + }, + "downloadFromBitwardenNow": { + "message": "Download from bitwarden.com now" + }, + "getItOnGooglePlay": { + "message": "Get it on Google Play" + }, + "downloadOnTheAppStore": { + "message": "Download on the App Store" + }, "permanentlyDeleteAttachmentConfirmation": { "message": "Are you sure you want to permanently delete this attachment?" }, @@ -4917,6 +5026,15 @@ "biometricsStatusHelptextUnavailableReasonUnknown": { "message": "Biometric unlock is currently unavailable for an unknown reason." }, + "unlockVault": { + "message": "Unlock your vault in seconds" + }, + "unlockVaultDesc": { + "message": "You can customize your unlock and timeout settings to more quickly access your vault." + }, + "unlockPinSet": { + "message": "Unlock PIN set" + }, "authenticating": { "message": "Authenticating" }, @@ -4928,8 +5046,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": { @@ -5079,42 +5197,6 @@ "beta": { "message": "Beta" }, - "importantNotice": { - "message": "Important notice" - }, - "setupTwoStepLogin": { - "message": "Set up two-step login" - }, - "newDeviceVerificationNoticeContentPage1": { - "message": "Bitwarden will send a code to your account email to verify logins from new devices starting in February 2025." - }, - "newDeviceVerificationNoticeContentPage2": { - "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" - }, - "newDeviceVerificationNoticePageOneFormContent": { - "message": "Do you have reliable access to your email, $EMAIL$?", - "placeholders": { - "email": { - "content": "$1", - "example": "your_name@email.com" - } - } - }, - "newDeviceVerificationNoticePageOneEmailAccessNo": { - "message": "No, I do not" - }, - "newDeviceVerificationNoticePageOneEmailAccessYes": { - "message": "Yes, I can reliably access my email" - }, - "turnOnTwoStepLogin": { - "message": "Turn on two-step login" - }, - "changeAcctEmail": { - "message": "Change account email" - }, "extensionWidth": { "message": "Extension width" }, @@ -5169,6 +5251,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 +5283,98 @@ }, "secureDevicesBody": { "message": "Save unlimited passwords across unlimited devices with Bitwarden mobile, browser, and desktop apps." + }, + "nudgeBadgeAria": { + "message": "1 notification" + }, + "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!" + }, + "hasItemsVaultNudgeBodyOne": { + "message": "Autofill items for the current page" + }, + "hasItemsVaultNudgeBodyTwo": { + "message": "Favorite items for easy access" + }, + "hasItemsVaultNudgeBodyThree": { + "message": "Search your vault for something else" + }, + "newLoginNudgeTitle": { + "message": "Save time with autofill" + }, + "newLoginNudgeBodyOne": { + "message": "Include a", + "description": "This is in multiple parts to allow for bold text in the middle of the sentence.", + "example": "Include a Website so this login appears as an autofill suggestion." + }, + "newLoginNudgeBodyBold": { + "message": "Website", + "description": "This is in multiple parts to allow for bold text in the middle of the sentence.", + "example": "Include a Website so this login appears as an autofill suggestion." + }, + "newLoginNudgeBodyTwo": { + "message": "so this login appears as an autofill suggestion.", + "description": "This is in multiple parts to allow for bold text in the middle of the sentence.", + "example": "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" + }, + "newSshNudgeBodyOne": { + "message": "Store your keys and connect with the SSH agent for fast, encrypted authentication.", + "description": "Two part message", + "example": "Store your keys and connect with the SSH agent for fast, encrypted authentication. Learn more about SSH agent" + }, + "newSshNudgeBodyTwo": { + "message": "Learn more about SSH agent", + "description": "Two part message", + "example": "Store your keys and connect with the SSH agent for fast, encrypted authentication. Learn more about SSH agent" + }, + "generatorNudgeTitle": { + "message": "Quickly create passwords" + }, + "generatorNudgeBodyOne": { + "message": "Easily create strong and unique passwords by clicking on", + "description": "Two part message", + "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." + }, + "generatorNudgeBodyTwo": { + "message": "to help you keep your logins secure.", + "description": "Two part message", + "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." + }, + "generatorNudgeBodyAria": { + "message": "Easily create strong and unique passwords by clicking on the Generate password button to help you keep your logins secure.", + "description": "Aria label for the body content of the generator nudge" + }, + "noPermissionsViewPage": { + "message": "You do not have permissions to view this page. Try logging in with a different account." } } diff --git a/apps/browser/src/_locales/my/messages.json b/apps/browser/src/_locales/my/messages.json index 7bec167e1c6..032d8c89d49 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,55 @@ "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.", + "notificationNewItemAria": { + "message": "New Item, opens in new window", + "description": "Aria label for the new item button in notification bar confirmation message when error is prompted" + }, + "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": { - "content": "$1" + "label": { + "content": "$1", + "example": "Login" } - }, - "description": "Shown to user after login is updated." + } + }, + "notificationLoginSaveConfirmation": { + "message": "saved to Bitwarden.", + "description": "Shown to user after item is saved." + }, + "notificationLoginUpdatedConfirmation": { + "message": "updated in Bitwarden.", + "description": "Shown to user after item is updated." + }, + "selectItemAriaLabel": { + "message": "Select $ITEMTYPE$, $ITEMNAME$", + "description": "Used by screen readers. $1 is the item type (like vault or folder), $2 is the selected item name.", + "placeholders": { + "itemType": { + "content": "$1" + }, + "itemName": { + "content": "$2" + } + } }, "saveAsNewLoginAction": { "message": "Save as new login", @@ -1082,12 +1120,16 @@ "message": "Update login", "description": "Button text for updating an existing login entry." }, - "saveLoginPrompt": { - "message": "Save login?", + "unlockToSave": { + "message": "Unlock to save this login", + "description": "User prompt to take action in order to save the login they just entered." + }, + "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": { @@ -1323,8 +1365,8 @@ "featureUnavailable": { "message": "Feature unavailable" }, - "encryptionKeyMigrationRequired": { - "message": "Encryption key migration required. Please login through the web vault to update your encryption key." + "legacyEncryptionUnsupported": { + "message": "Legacy encryption is no longer supported. Please contact support to recover your account." }, "premiumMembership": { "message": "Premium membership" @@ -1557,6 +1599,24 @@ "autofillSuggestionsSectionTitle": { "message": "Autofill suggestions" }, + "autofillSpotlightTitle": { + "message": "Easily find autofill suggestions" + }, + "autofillSpotlightDesc": { + "message": "Turn off your browser's autofill settings, so they don't conflict with Bitwarden." + }, + "turnOffBrowserAutofill": { + "message": "Turn off $BROWSER$ autofill", + "placeholders": { + "browser": { + "content": "$1", + "example": "Chrome" + } + } + }, + "turnOffAutofill": { + "message": "Turn off autofill" + }, "showInlineMenuLabel": { "message": "Show autofill suggestions on form fields" }, @@ -2144,6 +2204,9 @@ "useThisPassword": { "message": "Use this password" }, + "useThisPassphrase": { + "message": "Use this passphrase" + }, "useThisUsername": { "message": "Use this username" }, @@ -2167,15 +2230,6 @@ "vaultTimeoutAction1": { "message": "Timeout action" }, - "newCustomizationOptionsCalloutTitle": { - "message": "New customization options" - }, - "newCustomizationOptionsCalloutContent": { - "message": "Customize your vault experience with quick copy actions, compact mode, and more!" - }, - "newCustomizationOptionsCalloutLink": { - "message": "View all Appearance settings" - }, "lock": { "message": "Lock", "description": "Verb form: to make secure or inaccessible by" @@ -2623,6 +2677,10 @@ "message": "All Sends", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, + "maxAccessCountReached": { + "message": "Max access count reached", + "description": "This text will be displayed after a Send has been accessed the maximum amount of times." + }, "hideTextByDefault": { "message": "Hide text by default" }, @@ -2963,14 +3021,14 @@ "copyCustomFieldNameNotUnique": { "message": "No unique identifier found." }, - "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.", - "placeholders": { - "organization": { - "content": "$1", - "example": "My Org Name" - } - } + "removeMasterPasswordForOrganizationUserKeyConnector": { + "message": "A master password is no longer required for members of the following organization. Please confirm the domain below with your organization administrator." + }, + "organizationName": { + "message": "Organization name" + }, + "keyConnectorDomain": { + "message": "Key Connector domain" }, "leaveOrganization": { "message": "Leave organization" @@ -3532,12 +3590,36 @@ "deviceTrusted": { "message": "Device trusted" }, - "sendsNoItemsTitle": { - "message": "No active Sends", + "trustOrganization": { + "message": "Trust organization" + }, + "trust": { + "message": "Trust" + }, + "doNotTrust": { + "message": "Do not trust" + }, + "organizationNotTrusted": { + "message": "Organization is not trusted" + }, + "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" + }, + "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." + }, + "orgTrustWarning1": { + "message": "This organization has an Enterprise policy that will enroll you in account recovery. Enrollment will allow organization administrators to change your password. Only proceed if you recognize this organization and the fingerprint phrase displayed below matches the organization's fingerprint." + }, + "trustUser": { + "message": "Trust user" + }, + "sendsTitleNoItems": { + "message": "Send sensitive information safely", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, - "sendsNoItemsMessage": { - "message": "Use Send to securely share encrypted information with anyone.", + "sendsBodyNoItems": { + "message": "Share files and data securely with anyone, on any platform. Your information will remain end-to-end encrypted while limiting exposure.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "inputRequired": { @@ -4456,6 +4538,33 @@ } } }, + "downloadBitwarden": { + "message": "Download Bitwarden" + }, + "downloadBitwardenOnAllDevices": { + "message": "Download Bitwarden on all devices" + }, + "getTheMobileApp": { + "message": "Get the mobile app" + }, + "getTheMobileAppDesc": { + "message": "Access your passwords on the go with the Bitwarden mobile app." + }, + "getTheDesktopApp": { + "message": "Get the desktop app" + }, + "getTheDesktopAppDesc": { + "message": "Access your vault without a browser, then set up unlock with biometrics to expedite unlocking in both the desktop app and browser extension." + }, + "downloadFromBitwardenNow": { + "message": "Download from bitwarden.com now" + }, + "getItOnGooglePlay": { + "message": "Get it on Google Play" + }, + "downloadOnTheAppStore": { + "message": "Download on the App Store" + }, "permanentlyDeleteAttachmentConfirmation": { "message": "Are you sure you want to permanently delete this attachment?" }, @@ -4917,6 +5026,15 @@ "biometricsStatusHelptextUnavailableReasonUnknown": { "message": "Biometric unlock is currently unavailable for an unknown reason." }, + "unlockVault": { + "message": "Unlock your vault in seconds" + }, + "unlockVaultDesc": { + "message": "You can customize your unlock and timeout settings to more quickly access your vault." + }, + "unlockPinSet": { + "message": "Unlock PIN set" + }, "authenticating": { "message": "Authenticating" }, @@ -4928,8 +5046,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": { @@ -5079,42 +5197,6 @@ "beta": { "message": "Beta" }, - "importantNotice": { - "message": "Important notice" - }, - "setupTwoStepLogin": { - "message": "Set up two-step login" - }, - "newDeviceVerificationNoticeContentPage1": { - "message": "Bitwarden will send a code to your account email to verify logins from new devices starting in February 2025." - }, - "newDeviceVerificationNoticeContentPage2": { - "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" - }, - "newDeviceVerificationNoticePageOneFormContent": { - "message": "Do you have reliable access to your email, $EMAIL$?", - "placeholders": { - "email": { - "content": "$1", - "example": "your_name@email.com" - } - } - }, - "newDeviceVerificationNoticePageOneEmailAccessNo": { - "message": "No, I do not" - }, - "newDeviceVerificationNoticePageOneEmailAccessYes": { - "message": "Yes, I can reliably access my email" - }, - "turnOnTwoStepLogin": { - "message": "Turn on two-step login" - }, - "changeAcctEmail": { - "message": "Change account email" - }, "extensionWidth": { "message": "Extension width" }, @@ -5169,6 +5251,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 +5283,98 @@ }, "secureDevicesBody": { "message": "Save unlimited passwords across unlimited devices with Bitwarden mobile, browser, and desktop apps." + }, + "nudgeBadgeAria": { + "message": "1 notification" + }, + "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!" + }, + "hasItemsVaultNudgeBodyOne": { + "message": "Autofill items for the current page" + }, + "hasItemsVaultNudgeBodyTwo": { + "message": "Favorite items for easy access" + }, + "hasItemsVaultNudgeBodyThree": { + "message": "Search your vault for something else" + }, + "newLoginNudgeTitle": { + "message": "Save time with autofill" + }, + "newLoginNudgeBodyOne": { + "message": "Include a", + "description": "This is in multiple parts to allow for bold text in the middle of the sentence.", + "example": "Include a Website so this login appears as an autofill suggestion." + }, + "newLoginNudgeBodyBold": { + "message": "Website", + "description": "This is in multiple parts to allow for bold text in the middle of the sentence.", + "example": "Include a Website so this login appears as an autofill suggestion." + }, + "newLoginNudgeBodyTwo": { + "message": "so this login appears as an autofill suggestion.", + "description": "This is in multiple parts to allow for bold text in the middle of the sentence.", + "example": "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" + }, + "newSshNudgeBodyOne": { + "message": "Store your keys and connect with the SSH agent for fast, encrypted authentication.", + "description": "Two part message", + "example": "Store your keys and connect with the SSH agent for fast, encrypted authentication. Learn more about SSH agent" + }, + "newSshNudgeBodyTwo": { + "message": "Learn more about SSH agent", + "description": "Two part message", + "example": "Store your keys and connect with the SSH agent for fast, encrypted authentication. Learn more about SSH agent" + }, + "generatorNudgeTitle": { + "message": "Quickly create passwords" + }, + "generatorNudgeBodyOne": { + "message": "Easily create strong and unique passwords by clicking on", + "description": "Two part message", + "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." + }, + "generatorNudgeBodyTwo": { + "message": "to help you keep your logins secure.", + "description": "Two part message", + "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." + }, + "generatorNudgeBodyAria": { + "message": "Easily create strong and unique passwords by clicking on the Generate password button to help you keep your logins secure.", + "description": "Aria label for the body content of the generator nudge" + }, + "noPermissionsViewPage": { + "message": "You do not have permissions to view this page. Try logging in with a different account." } } diff --git a/apps/browser/src/_locales/nb/messages.json b/apps/browser/src/_locales/nb/messages.json index 7fa41eed68b..a20eb5b1b12 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)" @@ -14,7 +17,7 @@ "message": "Logg på eller opprett en ny konto for å få tilgang til ditt sikre hvelv." }, "inviteAccepted": { - "message": "Invitation accepted" + "message": "Invitasjon akseptert" }, "createAccount": { "message": "Opprett en konto" @@ -26,7 +29,7 @@ "message": "Logg inn med passnøkkel" }, "useSingleSignOn": { - "message": "Use single sign-on" + "message": "Bruk singulær pålogging" }, "welcomeBack": { "message": "Velkommen tilbake" @@ -35,7 +38,7 @@ "message": "Velg et sterkt passord" }, "finishCreatingYourAccountBySettingAPassword": { - "message": "Finish creating your account by setting a password" + "message": "Fullfør opprettelsen av kontoen din ved å bestemme et passord" }, "enterpriseSingleSignOn": { "message": "Bedriftsinnlogging (SSO)" @@ -56,10 +59,10 @@ "message": "Hovedpassord" }, "masterPassDesc": { - "message": "Superpassordet er passordet du bruker for å få tilgang til hvelvet ditt. Det er veldig viktig at du aldri glemmer ditt superpassord. Det er ingen måter å få tilbake passordet på dersom du noensinne skulle klare å glemme det." + "message": "Hovedpassordet er passordet du bruker for å få tilgang til hvelvet ditt. Det er veldig viktig at du aldri glemmer hovedpassordet ditt. Det er ingen måter å få tilbake passordet på dersom du skulle klare å glemme det." }, "masterPassHintDesc": { - "message": "Et hint for superpassordet kan hjelpe deg med å huske på passordet dersom du skulle glemme det." + "message": "Et hint for hovedpassordet kan hjelpe deg med å huske passordet dersom du skulle glemme det." }, "masterPassHintText": { "message": "If you forget your password, the password hint can be sent to your email. $CURRENT$/$MAXIMUM$ character maximum.", @@ -81,7 +84,7 @@ "message": "Et hint for hovedpassordet (valgfritt)" }, "passwordStrengthScore": { - "message": "Password strength score $SCORE$", + "message": "Passordstyrke-score: $SCORE$", "placeholders": { "score": { "content": "$1", @@ -129,7 +132,7 @@ "message": "Kopier passordet" }, "copyPassphrase": { - "message": "Copy passphrase" + "message": "Kopier passordfrase" }, "copyNote": { "message": "Kopier notatet" @@ -186,7 +189,7 @@ "message": "Kopiér notater" }, "copy": { - "message": "Copy", + "message": "Kopier", "description": "Copy to clipboard" }, "fill": { @@ -261,10 +264,10 @@ "message": "Be om passordhint" }, "enterYourAccountEmailAddressAndYourPasswordHintWillBeSentToYou": { - "message": "Enter your account email address and your password hint will be sent to you" + "message": "Skriv inn e-postadressen til kontoen din, så får du tilsendt et hint om passordet ditt" }, "getMasterPasswordHint": { - "message": "Få et hint om superpassordet" + "message": "Få et hint om hovedpassordet" }, "continue": { "message": "Fortsett" @@ -300,7 +303,7 @@ "message": "Learn more about how to use Bitwarden on the Help Center." }, "continueToBrowserExtensionStore": { - "message": "Continue to browser extension store?" + "message": "Vil du fortsette til nettleserutvidelsesbutikken?" }, "continueToBrowserExtensionStoreDesc": { "message": "Help others find out if Bitwarden is right for them. Visit your browser's extension store and leave a rating now." @@ -341,7 +344,7 @@ "message": "Bitwarden-autentiserer" }, "continueToAuthenticatorPageDesc": { - "message": "Bitwarden Authenticator allows you to store authenticator keys and generate TOTP codes for 2-step verification flows. Learn more on the bitwarden.com website" + "message": "Med Bitwarden Authenticator kan du lagre autentiseringsnøkler og generere TOTP-koder for 2-trinnspålogging. Les mer på nettstedet bitwarden.com" }, "bitwardenSecretsManager": { "message": "Bitwarden Secrets Manager" @@ -356,7 +359,7 @@ "message": "Create smooth and secure login experiences free from traditional passwords with Passwordless.dev. Learn more on the bitwarden.com website." }, "freeBitwardenFamilies": { - "message": "Free Bitwarden Families" + "message": "Gratis Bitwarden-familier" }, "freeBitwardenFamiliesPageDesc": { "message": "You are eligible for Free Bitwarden Families. Redeem this offer today in the web app." @@ -380,7 +383,7 @@ "message": "Rediger mappen" }, "editFolderWithName": { - "message": "Edit folder: $FOLDERNAME$", + "message": "Rediger mappe: $FOLDERNAME$", "placeholders": { "foldername": { "content": "$1", @@ -632,10 +635,10 @@ "message": "Opplåsingsalternativer" }, "unlockMethodNeededToChangeTimeoutActionDesc": { - "message": "Set up an unlock method to change your vault timeout action." + "message": "Sett opp en opplåsingsmetode for å endre tidsavbruddshandlingen for hvelvet." }, "unlockMethodNeeded": { - "message": "Set up an unlock method in Settings" + "message": "Sett opp en opplåsingsmetode i Innstillinger" }, "sessionTimeoutHeader": { "message": "Tidsavbrudd for økten" @@ -653,7 +656,7 @@ "message": "Nettleseren din støtter ikke kopiering til utklippstavlen på noe enkelt vis. Prøv å kopiere det manuelt i stedet." }, "verifyYourIdentity": { - "message": "Verify your identity" + "message": "Bekreft identiteten din" }, "weDontRecognizeThisDevice": { "message": "We don't recognize this device. Enter the code sent to your email to verify your identity." @@ -690,7 +693,7 @@ } }, "invalidMasterPassword": { - "message": "Ugyldig superpassord" + "message": "Ugyldig hovedpassord" }, "vaultTimeout": { "message": "Tidsavbrudd i hvelvet" @@ -787,7 +790,7 @@ } }, "masterPassDoesntMatch": { - "message": "Superpassord-bekreftelsen er ikke samsvarende." + "message": "Bekreftelsen på hovedpassordet samsvarer ikke." }, "newAccountCreated": { "message": "Din nye konto har blitt opprettet! Du kan nå logge på." @@ -805,7 +808,7 @@ "message": "Du kan lukke dette vinduet" }, "masterPassSent": { - "message": "Vi har sendt deg en E-post med hintet til superpassordet." + "message": "Vi har sendt deg en e-post med hintet til hovedpassordet." }, "verificationCodeRequired": { "message": "En verifiseringskode er påkrevd." @@ -839,7 +842,7 @@ "message": "Scan authenticator QR code from current webpage" }, "totpHelperTitle": { - "message": "Make 2-step verification seamless" + "message": "Gjør 2-trinnsbekreftelse sømløs" }, "totpHelper": { "message": "Bitwarden can store and fill 2-step verification codes. Copy and paste the key into this field." @@ -869,19 +872,22 @@ "message": "Logg inn på Bitwarden" }, "enterTheCodeSentToYourEmail": { - "message": "Enter the code sent to your email" + "message": "Skriv inn koden du har fått tilsendt på e-post" }, "enterTheCodeFromYourAuthenticatorApp": { - "message": "Enter the code from your authenticator app" + "message": "Skriv inn koden fra autentiseringsappen din" }, "pressYourYubiKeyToAuthenticate": { - "message": "Press your YubiKey to authenticate" + "message": "Trykk på YubiKey-en for å autentisere" }, "duoTwoFactorRequiredPageSubtitle": { "message": "Duo two-step login is required for your account. Follow the steps below to finish logging in." }, "followTheStepsBelowToFinishLoggingIn": { - "message": "Follow the steps below to finish logging in." + "message": "Følg trinnene nedenfor for å fullføre innloggingen." + }, + "followTheStepsBelowToFinishLoggingInWithSecurityKey": { + "message": "Følg trinnene nedenfor for å fullføre innloggingen med sikkerhetsnøkkelen din." }, "restartRegistration": { "message": "Start registreringen på nytt" @@ -905,7 +911,7 @@ "message": "Nei" }, "location": { - "message": "Location" + "message": "Sted" }, "unexpectedError": { "message": "En uventet feil har oppstått." @@ -1010,7 +1016,7 @@ "message": "Spør om å legge til innlogging" }, "vaultSaveOptionsTitle": { - "message": "Save to vault options" + "message": "Innstillinger for hvelvlagring" }, "addLoginNotificationDesc": { "message": "\"Legg til innlogging\"-beskjeden ber deg automatisk om å lagre nye innlogginger til hvelvet ditt hver gang du logger på dem for første gang." @@ -1019,7 +1025,7 @@ "message": "Ask to add an item if one isn't found in your vault. Applies to all logged in accounts." }, "showCardsInVaultViewV2": { - "message": "Always show cards as Autofill suggestions on Vault view" + "message": "Alltid vis kort som auto-utfyllingsforslag i Hvelv-visningen" }, "showCardsCurrentTab": { "message": "Vis kort på fanesiden" @@ -1028,7 +1034,7 @@ "message": "Vis kortelementer på fanesiden for lett auto-utfylling." }, "showIdentitiesInVaultViewV2": { - "message": "Always show identities as Autofill suggestions on Vault view" + "message": "Alltid vis identiteter som auto-utfyllingsforslag i Hvelv-visningen" }, "showIdentitiesCurrentTab": { "message": "Vis identiteter på fanesiden" @@ -1037,10 +1043,10 @@ "message": "Vis identitetselementer på fanesiden for enkel auto-utfylling." }, "clickToAutofillOnVault": { - "message": "Click items to autofill on Vault view" + "message": "Klikk på gjenstander som skal auto-utfylles i Hvelv-visningen" }, "clickToAutofill": { - "message": "Click items in autofill suggestion to fill" + "message": "Klikk på gjenstander i auto-utfyllingsforslagene for å utfylle" }, "clearClipboard": { "message": "Tøm utklippstavlen", @@ -1056,46 +1062,82 @@ "notificationAddSave": { "message": "Ja, lagre nå" }, - "loginSaveSuccessDetails": { - "message": "$USERNAME$ saved to Bitwarden.", + "notificationViewAria": { + "message": "Vis $ITEMNAME$, åpnes i nytt vindu", "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.", + "notificationNewItemAria": { + "message": "New Item, opens in new window", + "description": "Aria label for the new item button in notification bar confirmation message when error is prompted" + }, + "notificationEditTooltip": { + "message": "Rediger før du lagrer", + "description": "Tooltip and Aria label for edit button on cipher item" + }, + "newNotification": { + "message": "Nytt varsel" + }, + "labelWithNotification": { + "message": "$LABEL$: Nytt varsel", + "description": "Label for the notification with a new login suggestion.", "placeholders": { - "username": { - "content": "$1" + "label": { + "content": "$1", + "example": "Login" } - }, - "description": "Shown to user after login is updated." + } + }, + "notificationLoginSaveConfirmation": { + "message": "saved to Bitwarden.", + "description": "Shown to user after item is saved." + }, + "notificationLoginUpdatedConfirmation": { + "message": "updated in Bitwarden.", + "description": "Shown to user after item is updated." + }, + "selectItemAriaLabel": { + "message": "Select $ITEMTYPE$, $ITEMNAME$", + "description": "Used by screen readers. $1 is the item type (like vault or folder), $2 is the selected item name.", + "placeholders": { + "itemType": { + "content": "$1" + }, + "itemName": { + "content": "$2" + } + } }, "saveAsNewLoginAction": { - "message": "Save as new login", + "message": "Lagre som ny pålogging", "description": "Button text for saving login details as a new entry." }, "updateLoginAction": { - "message": "Update login", + "message": "Oppdater pålogging", "description": "Button text for updating an existing login entry." }, - "saveLoginPrompt": { - "message": "Save login?", + "unlockToSave": { + "message": "Unlock to save this login", + "description": "User prompt to take action in order to save the login they just entered." + }, + "saveLogin": { + "message": "Lagre pålogging", "description": "Prompt asking the user if they want to save their login details." }, - "updateLoginPrompt": { - "message": "Update existing login?", + "updateLogin": { + "message": "Oppdater eksisterende pålogging", "description": "Prompt asking the user if they want to update an existing login entry." }, "loginSaveSuccess": { - "message": "Login saved", + "message": "Påloggingen ble lagret", "description": "Message displayed when login details are successfully saved." }, "loginUpdateSuccess": { - "message": "Login updated", + "message": "Påloggingen ble oppdatert", "description": "Message displayed when login details are successfully updated." }, "loginUpdateTaskSuccess": { @@ -1124,7 +1166,7 @@ "description": "Message prompting user to undertake completion of another security task." }, "saveFailure": { - "message": "Error saving", + "message": "Feil under lagring", "description": "Error message shown when the system fails to save login details." }, "saveFailureDetails": { @@ -1141,7 +1183,7 @@ "message": "Ask to update a login's password when a change is detected on a website. Applies to all logged in accounts." }, "enableUsePasskeys": { - "message": "Ask to save and use passkeys" + "message": "Spør om å lagre og bruke passnøkler" }, "usePasskeysDesc": { "message": "Ask to save new passkeys or log in with passkeys stored in your vault. Applies to all logged in accounts." @@ -1153,7 +1195,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" @@ -1168,7 +1210,7 @@ "message": "Bruk et sekundært klikk for å få tilgang til passordgenerering og samsvarende innlogginger for nettsiden. " }, "contextMenuItemDescAlt": { - "message": "Use a secondary click to access password generation and matching logins for the website. Applies to all logged in accounts." + "message": "Bruk et andre klikk for å få tilgang til passordgenerering og samsvarende pålogginger for nettstedet. Gjelder for alle innloggede kontoer." }, "defaultUriMatchDetection": { "message": "Standard URI-samsvarsgjenkjenning", @@ -1195,7 +1237,7 @@ "description": "Light color" }, "exportFrom": { - "message": "Export from" + "message": "Eksporter fra" }, "exportVault": { "message": "Eksporter hvelvet" @@ -1225,7 +1267,7 @@ "message": "Account restricted" }, "filePasswordAndConfirmFilePasswordDoNotMatch": { - "message": "“File password” and “Confirm file password“ do not match." + "message": "«Filpassord» og «Bekreft filpassord» stemmer ikke overens." }, "warning": { "message": "ADVARSEL", @@ -1248,7 +1290,7 @@ "message": "Kontokrypteringsnøkler er unike for hver Bitwarden sin brukerkonto, og du kan ikke importere en kryptert eksport til en annen konto." }, "exportMasterPassword": { - "message": "Skriv inn ditt superpassord for å eksportere dine hvelvdataer." + "message": "Skriv inn hovedpassordet ditt for å eksportere dine hvelvdataer." }, "shared": { "message": "Delt" @@ -1323,8 +1365,8 @@ "featureUnavailable": { "message": "Egenskapen er utilgjengelig" }, - "encryptionKeyMigrationRequired": { - "message": "Encryption key migration required. Please login through the web vault to update your encryption key." + "legacyEncryptionUnsupported": { + "message": "Legacy encryption is no longer supported. Please contact support to recover your account." }, "premiumMembership": { "message": "Premium-medlemskap" @@ -1390,7 +1432,7 @@ } }, "premiumPriceV2": { - "message": "All for just $PRICE$ per year!", + "message": "Alt for bare $PRICE$ per år!", "placeholders": { "price": { "content": "$1", @@ -1417,7 +1459,7 @@ "message": "Et Premium-medlemskap er påkrevd for å bruke denne funksjonen." }, "authenticationTimeout": { - "message": "Authentication timeout" + "message": "Tidsavbrudd for autentisering" }, "authenticationSessionTimedOut": { "message": "The authentication session timed out. Please restart the login process." @@ -1432,26 +1474,26 @@ } }, "dontAskAgainOnThisDeviceFor30Days": { - "message": "Don't ask again on this device for 30 days" + "message": "Ikke spør igjen på denne enheten på 30 dager" }, "selectAnotherMethod": { - "message": "Select another method", + "message": "Velg en annen metode", "description": "Select another two-step login method" }, "useYourRecoveryCode": { - "message": "Use your recovery code" + "message": "Bruk gjenopprettingskoden din" }, "insertU2f": { "message": "Sett din sikkerhetsnøkkel inn i din datamaskins USB-uttak. Dersom den har en knapp, trykk på den." }, "openInNewTab": { - "message": "Open in new tab" + "message": "Åpne i ny fane" }, "webAuthnAuthenticate": { "message": "Autentiser WebAuthn" }, "readSecurityKey": { - "message": "Read security key" + "message": "Les sikkerhetsnøkkel" }, "awaitingSecurityKeyInteraction": { "message": "Awaiting security key interaction..." @@ -1469,7 +1511,7 @@ "message": "Alternativer for 2-trinnsinnlogging" }, "selectTwoStepLoginMethod": { - "message": "Select two-step login method" + "message": "Velg 2-trinnspåloggingsmetode" }, "recoveryCodeDesc": { "message": "Har du mistet tilgang til alle dine 2-trinnsleverandører? Bruk din gjenopprettingskode til å fjerne alle 2-trinnsleverandører fra din konto." @@ -1481,17 +1523,17 @@ "message": "Autentiseringsapp" }, "authenticatorAppDescV2": { - "message": "Enter a code generated by an authenticator app like Bitwarden Authenticator.", + "message": "Skriv inn en kode generert av en autentiseringsapp som Bitwarden Authenticator.", "description": "'Bitwarden Authenticator' is a product name and should not be translated." }, "yubiKeyTitleV2": { - "message": "Yubico OTP Security Key" + "message": "Yubico OTP-sikkerhetsnøkkel" }, "yubiKeyDesc": { "message": "Bruk en YubiKey for å få tilgang til kontoen din. Virker med enheter av typene YubiKey 4, 4 Nano, 4C, og NEO." }, "duoDescV2": { - "message": "Enter a code generated by Duo Security.", + "message": "Skriv inn en kode generert av Duo Security.", "description": "'Duo Security' and 'Duo Mobile' are product names and should not be translated." }, "duoOrganizationDesc": { @@ -1557,6 +1599,24 @@ "autofillSuggestionsSectionTitle": { "message": "Autoutfyllingsforslag" }, + "autofillSpotlightTitle": { + "message": "Easily find autofill suggestions" + }, + "autofillSpotlightDesc": { + "message": "Turn off your browser's autofill settings, so they don't conflict with Bitwarden." + }, + "turnOffBrowserAutofill": { + "message": "Turn off $BROWSER$ autofill", + "placeholders": { + "browser": { + "content": "$1", + "example": "Chrome" + } + } + }, + "turnOffAutofill": { + "message": "Turn off autofill" + }, "showInlineMenuLabel": { "message": "Vis autoutfyll-forslag i tekstbokser" }, @@ -1570,7 +1630,7 @@ "message": "Vis forslag når ikonet er valgt" }, "showInlineMenuOnFormFieldsDescAlt": { - "message": "Applies to all logged in accounts." + "message": "Gjelder for alle innloggede kontoer." }, "turnOffBrowserBuiltInPasswordManagerSettings": { "message": "Skru av din nettlesers innebygde passordbehandler for å unngå konflikter." @@ -1583,15 +1643,15 @@ "description": "Overlay setting select option for disabling autofill overlay" }, "autofillOverlayVisibilityOnFieldFocus": { - "message": "When field is selected (on focus)", + "message": "Når et felt er valgt (ved fokus)", "description": "Overlay appearance select option for showing the field on focus of the input element" }, "autofillOverlayVisibilityOnButtonClick": { - "message": "When autofill icon is selected", + "message": "Når auto-utfyllingsikonet er valgt", "description": "Overlay appearance select option for showing the field on click of the overlay icon" }, "enableAutoFillOnPageLoadSectionTitle": { - "message": "Autofill on page load" + "message": "Auto-utfyll ved sideinnlasting" }, "enableAutoFillOnPageLoad": { "message": "Aktiver auto-utfylling ved sideinnlastning" @@ -1663,7 +1723,7 @@ "message": "Dra for å sortere" }, "dragToReorder": { - "message": "Drag to reorder" + "message": "Dra for å omorganisere" }, "cfTypeText": { "message": "Tekst" @@ -2075,7 +2135,7 @@ "message": "Svakt hovedpassord" }, "weakMasterPasswordDesc": { - "message": "Superpassordet du har valgt er svakt. Du bør bruke et sterkt superpassord (eller en passordfrase) for å sikre Bitwarden-kontoen din på en forsvarlig måte. Er du sikker på at du vil bruke dette superpassordet?" + "message": "Hovedpassordet du har valgt er svakt. Du bør bruke et sterkt hovedpassord (eller en passordfrase) for å sikre Bitwarden-kontoen din på en forsvarlig måte. Er du sikker på at du vil bruke dette hovedpassordet?" }, "pin": { "message": "PIN", @@ -2144,6 +2204,9 @@ "useThisPassword": { "message": "Bruk dette passordet" }, + "useThisPassphrase": { + "message": "Use this passphrase" + }, "useThisUsername": { "message": "Bruk dette brukernavnet" }, @@ -2159,7 +2222,7 @@ "description": "This will be used as part of a larger sentence, broken up to include the generator icon. The full sentence will read 'Use the generator [GENERATOR_ICON] to create a strong unique password'" }, "vaultCustomization": { - "message": "Vault customization" + "message": "Tilpasning av hvelv" }, "vaultTimeoutAction": { "message": "Handling ved tidsavbrudd i hvelvet" @@ -2167,15 +2230,6 @@ "vaultTimeoutAction1": { "message": "Handling ved tidsavbrudd" }, - "newCustomizationOptionsCalloutTitle": { - "message": "New customization options" - }, - "newCustomizationOptionsCalloutContent": { - "message": "Customize your vault experience with quick copy actions, compact mode, and more!" - }, - "newCustomizationOptionsCalloutLink": { - "message": "View all Appearance settings" - }, "lock": { "message": "Lås", "description": "Verb form: to make secure or inaccessible by" @@ -2332,7 +2386,7 @@ "message": "Ok" }, "errorRefreshingAccessToken": { - "message": "Access Token Refresh Error" + "message": "Feil ved oppdatering av tilgangssjetongen" }, "errorRefreshingAccessTokenDesc": { "message": "No refresh token or API keys found. Please try logging out and logging back in." @@ -2374,7 +2428,7 @@ "message": "Kontoen eksisterer ikke" }, "nativeMessagingWrongUserKeyTitle": { - "message": "Biometric key missmatch" + "message": "Biometrisk nøkkel samsvarer ikke" }, "nativeMessagingWrongUserKeyDesc": { "message": "Biometric unlock failed. The biometric secret key failed to unlock the vault. Please try to set up biometrics again." @@ -2392,10 +2446,10 @@ "message": "Biometri i nettleseren støttes ikke på denne enheten." }, "biometricsNotUnlockedTitle": { - "message": "User locked or logged out" + "message": "Brukeren er låst eller avlogget" }, "biometricsNotUnlockedDesc": { - "message": "Please unlock this user in the desktop application and try again." + "message": "Vennligst lås opp denne brukeren i skrivebordsprogrammet og prøv igjen." }, "biometricsNotAvailableTitle": { "message": "Biometrisk opplåsing er utilgjengelig" @@ -2428,7 +2482,7 @@ "message": "En virksomhetsregel påvirker dine eierskapsinnstillinger." }, "personalOwnershipPolicyInEffectImports": { - "message": "An organization policy has blocked importing items into your individual vault." + "message": "En organisasjonsretningslinje har blokkert import av gjenstander til ditt individuelle hvelv." }, "domainsTitle": { "message": "Domener", @@ -2438,7 +2492,7 @@ "message": "Blokkerte domener" }, "learnMoreAboutBlockedDomains": { - "message": "Learn more about blocked domains" + "message": "Finn ut mer om blokkerte domener" }, "excludedDomains": { "message": "Ekskluderte domener" @@ -2453,16 +2507,16 @@ "message": "Autofill and other related features will not be offered for these websites. You must refresh the page for changes to take effect." }, "autofillBlockedNoticeV2": { - "message": "Autofill is blocked for this website." + "message": "Auto-utfylling er blokkert for dette nettstedet." }, "autofillBlockedNoticeGuidance": { "message": "Endre dette i innstillingene" }, "change": { - "message": "Change" + "message": "Endre" }, "changeButtonTitle": { - "message": "Change password - $ITEMNAME$", + "message": "Endre passord - $ITEMNAME$", "placeholders": { "itemname": { "content": "$1", @@ -2543,7 +2597,7 @@ "message": "Illustration of the Bitwarden autofill menu displaying a generated password." }, "updateInBitwarden": { - "message": "Update in Bitwarden" + "message": "Oppdater i Bitwarden" }, "updateInBitwardenSlideDesc": { "message": "Bitwarden will then prompt you to update the password in the password manager.", @@ -2553,13 +2607,13 @@ "message": "Illustration of a Bitwarden’s notification prompting the user to update the login." }, "turnOnAutofill": { - "message": "Turn on autofill" + "message": "Skru på auto-utfylling" }, "turnedOnAutofill": { - "message": "Turned on autofill" + "message": "Skrudde på auto-utfylling" }, "dismiss": { - "message": "Dismiss" + "message": "Avvis" }, "websiteItemLabel": { "message": "Nettsted $number$ (URİ)", @@ -2623,6 +2677,10 @@ "message": "Alle Send-er", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, + "maxAccessCountReached": { + "message": "Max access count reached", + "description": "This text will be displayed after a Send has been accessed the maximum amount of times." + }, "hideTextByDefault": { "message": "Skjul tekst som standard" }, @@ -2811,7 +2869,7 @@ "message": "Forespørsel om hovedpassord på nytt" }, "passwordConfirmation": { - "message": "Hovedpassord bekreftelse" + "message": "Hovedpassord-bekreftelse" }, "passwordConfirmationDesc": { "message": "Denne handlingen er beskyttet. For å fortsette, skriv inn hovedpassordet på nytt for å bekrefte identiteten din." @@ -2897,7 +2955,7 @@ } }, "vaultTimeoutPolicyInEffect1": { - "message": "$HOURS$ hour(s) and $MINUTES$ minute(s) maximum.", + "message": "Maks $HOURS$ time(r) og $MINUTES$ minutt(er).", "placeholders": { "hours": { "content": "$1", @@ -2963,14 +3021,14 @@ "copyCustomFieldNameNotUnique": { "message": "Ingen unik identifikator ble funnet." }, - "convertOrganizationEncryptionDesc": { - "message": "$ORGANIZATION$ bruker SSO med en selvdrevet nøkkelserver. Et hovedpassord er ikke lenger nødvendig for å logge inn for medlemmer av denne organisasjonen.", - "placeholders": { - "organization": { - "content": "$1", - "example": "My Org Name" - } - } + "removeMasterPasswordForOrganizationUserKeyConnector": { + "message": "A master password is no longer required for members of the following organization. Please confirm the domain below with your organization administrator." + }, + "organizationName": { + "message": "Organization name" + }, + "keyConnectorDomain": { + "message": "Key Connector domain" }, "leaveOrganization": { "message": "Forlat organisasjonen" @@ -3185,7 +3243,7 @@ } }, "forwaderInvalidOperationWithMessage": { - "message": "$SERVICENAME$ refused your request: $ERRORMESSAGE$", + "message": "$SERVICENAME$ avslo forespørselen din: $ERRORMESSAGE$", "description": "Displayed when the user is forbidden from using the API by the forwarding service with an error message.", "placeholders": { "servicename": { @@ -3219,7 +3277,7 @@ } }, "forwarderNoUrl": { - "message": "Invalid $SERVICENAME$ url.", + "message": "Ugyldig $SERVICENAME$-url.", "description": "Displayed when the url of the forwarding service wasn't supplied.", "placeholders": { "servicename": { @@ -3334,7 +3392,7 @@ "message": "Et varsel er sendt til enheten din." }, "notificationSentDevicePart1": { - "message": "Unlock Bitwarden on your device or on the" + "message": "Lås opp Bitwarden på enheten din eller på" }, "notificationSentDeviceAnchor": { "message": "nett-app" @@ -3412,7 +3470,7 @@ "message": "Innstillinger for auto-utfylling" }, "autofillKeyboardShortcutSectionTitle": { - "message": "Autofill shortcut" + "message": "Auto-utfyllingshurtigtast(er)" }, "autofillKeyboardShortcutUpdateLabel": { "message": "Endre snarvei" @@ -3424,7 +3482,7 @@ "message": "Auto-utfyll tastatursnarvei" }, "autofillLoginShortcutNotSet": { - "message": "The autofill login shortcut is not set. Change this in the browser's settings." + "message": "Snarveien for auto-utfyllingspålogging er ikke angitt. Endre dette i nettleserens innstillinger." }, "autofillLoginShortcutText": { "message": "Autoutfyll-snarveien for pålogging er $COMMAND$. Håndter alle snarveiene i nettleserens innstillinger.", @@ -3457,7 +3515,7 @@ "message": "Enhetsgodkjennelse kreves" }, "selectAnApprovalOptionBelow": { - "message": "Select an approval option below" + "message": "Velg en godkjenningsmetode nedenfor" }, "rememberThisDevice": { "message": "Husk denne enheten" @@ -3524,7 +3582,7 @@ "message": "Innlogging godkjent" }, "userEmailMissing": { - "message": "User email missing" + "message": "Brukerens e-postadresse mangler" }, "activeUserEmailNotFoundLoggingYouOut": { "message": "Active user email not found. Logging you out." @@ -3532,12 +3590,36 @@ "deviceTrusted": { "message": "Enheten er betrodd" }, - "sendsNoItemsTitle": { - "message": "Ingen aktive Send", + "trustOrganization": { + "message": "Stol på organisasjon" + }, + "trust": { + "message": "Stol på" + }, + "doNotTrust": { + "message": "Ikke stol på" + }, + "organizationNotTrusted": { + "message": "Organisasjonen har ikke blitt stolt på" + }, + "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" + }, + "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." + }, + "orgTrustWarning1": { + "message": "This organization has an Enterprise policy that will enroll you in account recovery. Enrollment will allow organization administrators to change your password. Only proceed if you recognize this organization and the fingerprint phrase displayed below matches the organization's fingerprint." + }, + "trustUser": { + "message": "Stol på brukler" + }, + "sendsTitleNoItems": { + "message": "Send sensitive information safely", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, - "sendsNoItemsMessage": { - "message": "Bruk Send til å dele kryptert informasjon med noen.", + "sendsBodyNoItems": { + "message": "Share files and data securely with anyone, on any platform. Your information will remain end-to-end encrypted while limiting exposure.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "inputRequired": { @@ -3595,17 +3677,17 @@ } }, "multipleInputEmails": { - "message": "1 or more emails are invalid" + "message": "1 eller flere e-postadresser er ugyldige" }, "inputTrimValidator": { - "message": "Input must not contain only whitespace.", + "message": "Inndataen kan ikke bare inneholde tomrom.", "description": "Notification to inform the user that a form's input can't contain only whitespace." }, "inputEmail": { "message": "Inndataen er ikke en E-postadresse." }, "fieldsNeedAttention": { - "message": "$COUNT$ field(s) above need your attention.", + "message": "$COUNT$ felt(er) ovenfor trenger din oppmerksomhet.", "placeholders": { "count": { "content": "$1", @@ -3672,7 +3754,7 @@ "description": "Message appearing below the autofill on load message when master password reprompt is set for a vault item." }, "toggleSideNavigation": { - "message": "Toggle side navigation" + "message": "Skru av/på sidenavigering" }, "skipToContent": { "message": "Hopp frem til innholdet" @@ -3682,7 +3764,7 @@ "description": "Page title for the iframe containing the overlay button" }, "toggleBitwardenVaultOverlay": { - "message": "Toggle Bitwarden autofill menu", + "message": "Skru på/av Bitwardens auto-utfyllmeny", "description": "Screen reader and tool tip label for the overlay button" }, "bitwardenVault": { @@ -3690,11 +3772,11 @@ "description": "Page title in overlay" }, "unlockYourAccountToViewMatchingLogins": { - "message": "Unlock your account to view matching logins", + "message": "Lås opp kontoen din for å se samsvarende pålogginger", "description": "Text to display in overlay when the account is locked." }, "unlockYourAccountToViewAutofillSuggestions": { - "message": "Unlock your account to view autofill suggestions", + "message": "Lås opp kontoen din for å vise auto-utfyllingsforslag", "description": "Text to display in overlay when the account is locked." }, "unlockAccount": { @@ -3702,11 +3784,11 @@ "description": "Button text to display in overlay when the account is locked." }, "unlockAccountAria": { - "message": "Unlock your account, opens in a new window", + "message": "Lås opp kontoen din, åpnes i et nytt vindu", "description": "Screen reader text (aria-label) for unlock account button in overlay" }, "totpCodeAria": { - "message": "Time-based One-Time Password Verification Code", + "message": "Tidsbasert engangskode for bekreftelse av passord", "description": "Aria label for the totp code displayed in the inline menu for autofill" }, "totpSecondsSpanAria": { @@ -3714,7 +3796,7 @@ "description": "Aria label for the totp seconds displayed in the inline menu for autofill" }, "fillCredentialsFor": { - "message": "Fill credentials for", + "message": "Fyll ut innloggingsdetaljer for", "description": "Screen reader text for when overlay item is in focused" }, "partialUsername": { @@ -3738,7 +3820,7 @@ "description": "Button text to display within inline menu when there are no matching items on a login field" }, "addNewLoginItemAria": { - "message": "Add new vault login item, opens in a new window", + "message": "Legg til ny hvelvpåloggingsgjenstand, åpnes i et nytt vindu", "description": "Screen reader text (aria-label) for new login button within inline menu" }, "newCard": { @@ -3746,7 +3828,7 @@ "description": "Button text to display within inline menu when there are no matching items on a credit card field" }, "addNewCardItemAria": { - "message": "Add new vault card item, opens in a new window", + "message": "Legg til ny hvelvkortgjenstand, åpnes i et nytt vindu", "description": "Screen reader text (aria-label) for new card button within inline menu" }, "newIdentity": { @@ -3754,11 +3836,11 @@ "description": "Button text to display within inline menu when there are no matching items on an identity field" }, "addNewIdentityItemAria": { - "message": "Add new vault identity item, opens in a new window", + "message": "Legg til ny hvelvinnstillingsgjenstand, åpnes i et nytt vindu", "description": "Screen reader text (aria-label) for new identity button within inline menu" }, "bitwardenOverlayMenuAvailable": { - "message": "Bitwarden autofill menu available. Press the down arrow key to select.", + "message": "Bitwarden-autoutfyllingsmenyen er tilgjengelig. Trykk på «Piltast ned»-knappen for å velge.", "description": "Screen reader text for announcing when the overlay opens on the page" }, "turnOn": { @@ -3847,16 +3929,16 @@ "message": "Error connecting with the Duo service. Use a different two-step login method or contact Duo for assistance." }, "duoRequiredForAccount": { - "message": "Duo two-step login is required for your account." + "message": "Duo-totrinnspålogging er påkrevd for kontoen din." }, "popoutExtension": { - "message": "Popout extension" + "message": "Sprett ut utvidelse" }, "launchDuo": { "message": "Start Duo" }, "importFormatError": { - "message": "Data is not formatted correctly. Please check your import file and try again." + "message": "Dataene er ikke formatert riktig. Sjekk importfilen og prøv på nytt." }, "importNothingError": { "message": "Ingenting ble importert." @@ -3865,7 +3947,7 @@ "message": "Error decrypting the exported file. Your encryption key does not match the encryption key used export the data." }, "invalidFilePassword": { - "message": "Invalid file password, please use the password you entered when you created the export file." + "message": "Ugyldig filpassord, vennligst bruk passordet du skrev inn da du opprettet eksportfilen." }, "destination": { "message": "Destinasjon" @@ -3921,7 +4003,7 @@ "message": "Bekreft hvelvimportering" }, "confirmVaultImportDesc": { - "message": "This file is password-protected. Please enter the file password to import data." + "message": "Denne filen er passordbeskyttet. Vennligst skriv inn filpassordet for å importere data." }, "confirmFilePassword": { "message": "Bekreft filpassord" @@ -3948,19 +4030,19 @@ "message": "Verification required by the initiating site. This feature is not yet implemented for accounts without master password." }, "logInWithPasskeyQuestion": { - "message": "Log in with passkey?" + "message": "Vil du logge inn med passnøkkel?" }, "passkeyAlreadyExists": { - "message": "A passkey already exists for this application." + "message": "En passnøkkel finnes allerede for dette programmet." }, "noPasskeysFoundForThisApplication": { - "message": "No passkeys found for this application." + "message": "Ingen passnøkler ble funnet for dette programmet." }, "noMatchingPasskeyLogin": { "message": "Du har ikke en samsvarende innlogging for dette nettstedet." }, "noMatchingLoginsForSite": { - "message": "No matching logins for this site" + "message": "Ingen samsvarende pålogginger for dette nettstedet" }, "searchSavePasskeyNewLogin": { "message": "Søk eller lagre passnøkkelen som en ny innlogging" @@ -3975,16 +4057,16 @@ "message": "Lagre passnøkkelen som en ny pålogging" }, "chooseCipherForPasskeySave": { - "message": "Choose a login to save this passkey to" + "message": "Velg en pålogging å lagre denne passnøkkelen til" }, "chooseCipherForPasskeyAuth": { - "message": "Choose a passkey to log in with" + "message": "Velg en passnøkkel å logge inn med" }, "passkeyItem": { "message": "Passkode-gjenstand" }, "overwritePasskey": { - "message": "Overwrite passkey?" + "message": "Vil du overskrive passnøkkelen?" }, "overwritePasskeyAlert": { "message": "This item already contains a passkey. Are you sure you want to overwrite the current passkey?" @@ -3993,7 +4075,7 @@ "message": "Funksjonen støttes ikke ennå" }, "yourPasskeyIsLocked": { - "message": "Authentication required to use passkey. Verify your identity to continue." + "message": "Autentisering kreves for å bruke passnøkkel. Bekreft identiteten din for å fortsette." }, "multifactorAuthenticationCancelled": { "message": "Multifaktorautentisering ble avbrutt" @@ -4029,7 +4111,7 @@ "message": "LastPass-multifaktorautentisering kreves" }, "lastPassMFADesc": { - "message": "Enter your one-time passcode from your authentication app" + "message": "Skriv inn engangs-passkoden fra autentiseringsappen din" }, "lastPassOOBDesc": { "message": "Approve the login request in your authentication app or enter a one-time passcode." @@ -4081,7 +4163,7 @@ "message": "Aktiv konto" }, "bitwardenAccount": { - "message": "Bitwarden account" + "message": "Bitwarden-konto" }, "availableAccounts": { "message": "Tilgjengelige kontoer" @@ -4163,7 +4245,7 @@ "description": "Label for the setting that allows overriding the default browser autofill settings" }, "privacyPermissionAdditionNotGrantedTitle": { - "message": "Unable to set Bitwarden as the default password manager", + "message": "Klarte ikke å sette Bitwarden som standard passordbehandler", "description": "Title for the dialog that appears when the user has not granted the extension permission to set privacy settings" }, "privacyPermissionAdditionNotGrantedDescription": { @@ -4175,7 +4257,7 @@ "description": "Button text for the setting that allows overriding the default browser autofill settings" }, "saveCipherAttemptSuccess": { - "message": "Credentials saved successfully!", + "message": "Legitimasjonen ble vellykket lagret!", "description": "Notification message for when saving credentials has succeeded." }, "passwordSaved": { @@ -4183,7 +4265,7 @@ "description": "Notification message for when saving credentials has succeeded." }, "updateCipherAttemptSuccess": { - "message": "Credentials updated successfully!", + "message": "Legitimasjonen ble vellykket oppdatert!", "description": "Notification message for when updating credentials has succeeded." }, "passwordUpdated": { @@ -4201,7 +4283,7 @@ "message": "Fjern passordnøkkel" }, "passkeyRemoved": { - "message": "Passkey removed" + "message": "Passnøkkelen ble fjernet" }, "autofillSuggestions": { "message": "Autoutfyllingsforslag" @@ -4210,16 +4292,16 @@ "message": "Foreslåtte gjenstander" }, "autofillSuggestionsTip": { - "message": "Save a login item for this site to autofill" + "message": "Lagre en påloggingsgjenstand for dette nettstedet for å auto-utfylle" }, "yourVaultIsEmpty": { "message": "Hvelvet ditt er tomt" }, "noItemsMatchSearch": { - "message": "No items match your search" + "message": "Ingen gjenstander samsvarer med søket ditt" }, "clearFiltersOrTryAnother": { - "message": "Clear filters or try another search term" + "message": "Tøm filtrene eller prøv et annet søkeuttrykk" }, "copyInfoTitle": { "message": "Kopiér info - $ITEMNAME$", @@ -4272,7 +4354,7 @@ } }, "viewItemTitleWithField": { - "message": "View item - $ITEMNAME$ - $FIELD$", + "message": "Vis gjenstand - $ITEMNAME$ - $FIELD$", "description": "Title for a link that opens a view for an item.", "placeholders": { "itemname": { @@ -4296,7 +4378,7 @@ } }, "autofillTitleWithField": { - "message": "Autofill - $ITEMNAME$ - $FIELD$", + "message": "Auto-utfyll - $ITEMNAME$ - $FIELD$", "description": "Title for a button that autofills a login item.", "placeholders": { "itemname": { @@ -4310,7 +4392,7 @@ } }, "copyFieldValue": { - "message": "Copy $FIELD$, $VALUE$", + "message": "Kopier $FIELD$, $VALUE$", "description": "Title for a button that copies a field value to the clipboard.", "placeholders": { "field": { @@ -4330,7 +4412,7 @@ "message": "Legg til i samlinger" }, "copyEmail": { - "message": "Copy email" + "message": "Kopier e-postadresse" }, "copyPhone": { "message": "Kopiér telefonnummer" @@ -4339,7 +4421,7 @@ "message": "Kopiér adresse" }, "adminConsole": { - "message": "Admin Console" + "message": "Administrasjonskonsoll" }, "accountSecurity": { "message": "Kontosikkerhet" @@ -4399,7 +4481,7 @@ "message": "Gjenstandens navn" }, "organizationIsDeactivated": { - "message": "Organization is deactivated" + "message": "Organisasjonen er deaktivert" }, "owner": { "message": "Eier" @@ -4456,6 +4538,33 @@ } } }, + "downloadBitwarden": { + "message": "Download Bitwarden" + }, + "downloadBitwardenOnAllDevices": { + "message": "Download Bitwarden on all devices" + }, + "getTheMobileApp": { + "message": "Get the mobile app" + }, + "getTheMobileAppDesc": { + "message": "Access your passwords on the go with the Bitwarden mobile app." + }, + "getTheDesktopApp": { + "message": "Get the desktop app" + }, + "getTheDesktopAppDesc": { + "message": "Access your vault without a browser, then set up unlock with biometrics to expedite unlocking in both the desktop app and browser extension." + }, + "downloadFromBitwardenNow": { + "message": "Download from bitwarden.com now" + }, + "getItOnGooglePlay": { + "message": "Get it on Google Play" + }, + "downloadOnTheAppStore": { + "message": "Download on the App Store" + }, "permanentlyDeleteAttachmentConfirmation": { "message": "Are you sure you want to permanently delete this attachment?" }, @@ -4469,7 +4578,7 @@ "message": "Filtre" }, "filterVault": { - "message": "Filter vault" + "message": "Filtrer hvelv" }, "filterApplied": { "message": "Ett filter er benyttet" @@ -4565,7 +4674,7 @@ } }, "autoFillOnPageLoad": { - "message": "Autofill on page load?" + "message": "Vil du auto-utfylle ved sideinnlasting?" }, "cardExpiredTitle": { "message": "Utløpt kort" @@ -4649,7 +4758,7 @@ "message": "Bruk tekstfelter for data som sikkerhetsspørsmål" }, "hiddenHelpText": { - "message": "Use hidden fields for sensitive data like a password" + "message": "Bruk skjulte felter for sensitive data, for eksempel passord" }, "checkBoxHelpText": { "message": "Use checkboxes if you'd like to autofill a form's checkbox, like a remember email" @@ -4816,7 +4925,7 @@ "message": "Kontohandlinger" }, "showNumberOfAutofillSuggestions": { - "message": "Show number of login autofill suggestions on extension icon" + "message": "Vis antall auto-utfyllingsforslag for pålogging på utvidelsesikonet" }, "showQuickCopyActions": { "message": "Vis hurtigkopieringshandlinger i hvelvet" @@ -4888,22 +4997,22 @@ "message": "Slett for alltid" }, "noEditPermissions": { - "message": "You don't have permission to edit this item" + "message": "Du har ikke tillatelse til å redigere denne gjenstanden" }, "biometricsStatusHelptextUnlockNeeded": { - "message": "Biometric unlock is unavailable because PIN or password unlock is required first." + "message": "Biometrisk opplåsing er ikke tilgjengelig fordi PIN-kode eller passord kreves først." }, "biometricsStatusHelptextHardwareUnavailable": { "message": "Biometrisk opplåsing er utilgjengelig for øyeblikket." }, "biometricsStatusHelptextAutoSetupNeeded": { - "message": "Biometric unlock is unavailable due to misconfigured system files." + "message": "Biometrisk opplåsing er ikke tilgjengelig på grunn av feilkonfigurerte systemfiler." }, "biometricsStatusHelptextManualSetupNeeded": { - "message": "Biometric unlock is unavailable due to misconfigured system files." + "message": "Biometrisk opplåsing er ikke tilgjengelig på grunn av feilkonfigurerte systemfiler." }, "biometricsStatusHelptextDesktopDisconnected": { - "message": "Biometric unlock is unavailable because the Bitwarden desktop app is closed." + "message": "Biometrisk opplåsing er ikke tilgjengelig fordi Bitwarden-skrivebordsappen er lukket." }, "biometricsStatusHelptextNotEnabledInDesktop": { "message": "Biometric unlock is unavailable because it is not enabled for $EMAIL$ in the Bitwarden desktop app.", @@ -4917,6 +5026,15 @@ "biometricsStatusHelptextUnavailableReasonUnknown": { "message": "Biometric unlock is currently unavailable for an unknown reason." }, + "unlockVault": { + "message": "Unlock your vault in seconds" + }, + "unlockVaultDesc": { + "message": "You can customize your unlock and timeout settings to more quickly access your vault." + }, + "unlockPinSet": { + "message": "Unlock PIN set" + }, "authenticating": { "message": "Autentiserer" }, @@ -4928,8 +5046,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": "Lagre til Bitwarden", "description": "Confirmation message for saving a login to Bitwarden" }, "spaceCharacterDescriptor": { @@ -4953,7 +5071,7 @@ "description": "Represents the @ key in screen reader content as a readable word" }, "hashSignCharacterDescriptor": { - "message": "Hash sign", + "message": "Hashtag-symbol", "description": "Represents the # key in screen reader content as a readable word" }, "dollarSignCharacterDescriptor": { @@ -4997,7 +5115,7 @@ "description": "Represents the + key in screen reader content as a readable word" }, "equalsCharacterDescriptor": { - "message": "Equals", + "message": "Er lik", "description": "Represents the = key in screen reader content as a readable word" }, "braceLeftCharacterDescriptor": { @@ -5017,7 +5135,7 @@ "description": "Represents the ] key in screen reader content as a readable word" }, "pipeCharacterDescriptor": { - "message": "Pipe", + "message": "Rør", "description": "Represents the | key in screen reader content as a readable word" }, "backSlashCharacterDescriptor": { @@ -5079,42 +5197,6 @@ "beta": { "message": "Beta" }, - "importantNotice": { - "message": "Viktig melding" - }, - "setupTwoStepLogin": { - "message": "Sett opp 2-trinnspålogging" - }, - "newDeviceVerificationNoticeContentPage1": { - "message": "Bitwarden will send a code to your account email to verify logins from new devices starting in February 2025." - }, - "newDeviceVerificationNoticeContentPage2": { - "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": "Minn meg på det senere" - }, - "newDeviceVerificationNoticePageOneFormContent": { - "message": "Do you have reliable access to your email, $EMAIL$?", - "placeholders": { - "email": { - "content": "$1", - "example": "your_name@email.com" - } - } - }, - "newDeviceVerificationNoticePageOneEmailAccessNo": { - "message": "Nei, det gjør jeg ikke" - }, - "newDeviceVerificationNoticePageOneEmailAccessYes": { - "message": "Yes, I can reliably access my email" - }, - "turnOnTwoStepLogin": { - "message": "Slå på 2-trinnsinnlogging" - }, - "changeAcctEmail": { - "message": "Endre kontoens E-postadresse" - }, "extensionWidth": { "message": "Utvidelsens bredde" }, @@ -5125,31 +5207,31 @@ "message": "Ekstra bred" }, "sshKeyWrongPassword": { - "message": "The password you entered is incorrect." + "message": "Passordet du skrev inn er feil." }, "importSshKey": { - "message": "Import" + "message": "Importer" }, "confirmSshKeyPassword": { - "message": "Confirm password" + "message": "Bekreft passordet" }, "enterSshKeyPasswordDesc": { "message": "Enter the password for the SSH key." }, "enterSshKeyPassword": { - "message": "Enter password" + "message": "Skriv inn passord" }, "invalidSshKey": { - "message": "The SSH key is invalid" + "message": "SSH-nøkkelen er ugyldig" }, "sshKeyTypeUnsupported": { "message": "The SSH key type is not supported" }, "importSshKeyFromClipboard": { - "message": "Import key from clipboard" + "message": "Importer nøkkel fra utklippstavlen" }, "sshKeyImported": { - "message": "SSH key imported successfully" + "message": "SSH-nøkkelen ble vellykket importert" }, "cannotRemoveViewOnlyCollections": { "message": "You cannot remove collections with View only permissions: $COLLECTIONS$", @@ -5169,17 +5251,23 @@ "changeAtRiskPassword": { "message": "Change at-risk password" }, + "settingsVaultOptions": { + "message": "Hvelvinnstillinger" + }, + "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": "Velkommen til Bitwarden" }, "securityPrioritized": { - "message": "Security, prioritized" + "message": "Sikkerhet, prioritert" }, "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." }, "quickLogin": { - "message": "Quick and easy login" + "message": "Rask og enkel pålogging" }, "quickLoginBody": { "message": "Set up biometric unlock and autofill to log into your accounts without typing a single letter." @@ -5191,9 +5279,102 @@ "message": "Use the generator to create and save strong, unique passwords for all your accounts." }, "secureDevices": { - "message": "Your data, when and where you need it" + "message": "Dine data, når og hvor du trenger dem" }, "secureDevicesBody": { "message": "Save unlimited passwords across unlimited devices with Bitwarden mobile, browser, and desktop apps." + }, + "nudgeBadgeAria": { + "message": "1 notification" + }, + "emptyVaultNudgeTitle": { + "message": "Importer eksisterende passord" + }, + "emptyVaultNudgeBody": { + "message": "Use the importer to quickly transfer logins to Bitwarden without manually adding them." + }, + "emptyVaultNudgeButton": { + "message": "Importér nå" + }, + "hasItemsVaultNudgeTitle": { + "message": "Velkommen til hvelvet ditt!" + }, + "hasItemsVaultNudgeBodyOne": { + "message": "Autofill items for the current page" + }, + "hasItemsVaultNudgeBodyTwo": { + "message": "Favorite items for easy access" + }, + "hasItemsVaultNudgeBodyThree": { + "message": "Search your vault for something else" + }, + "newLoginNudgeTitle": { + "message": "Spar tid med auto-utfylling" + }, + "newLoginNudgeBodyOne": { + "message": "Include a", + "description": "This is in multiple parts to allow for bold text in the middle of the sentence.", + "example": "Include a Website so this login appears as an autofill suggestion." + }, + "newLoginNudgeBodyBold": { + "message": "Website", + "description": "This is in multiple parts to allow for bold text in the middle of the sentence.", + "example": "Include a Website so this login appears as an autofill suggestion." + }, + "newLoginNudgeBodyTwo": { + "message": "so this login appears as an autofill suggestion.", + "description": "This is in multiple parts to allow for bold text in the middle of the sentence.", + "example": "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": "Forenkle oppretting av kontoer" + }, + "newIdentityNudgeBody": { + "message": "With identities, quickly autofill long registration or contact forms." + }, + "newNoteNudgeTitle": { + "message": "Beskytt dine sensitive data" + }, + "newNoteNudgeBody": { + "message": "With notes, securely store sensitive data like banking or insurance details." + }, + "newSshNudgeTitle": { + "message": "Utviklervennlig SSH-tilgang" + }, + "newSshNudgeBodyOne": { + "message": "Store your keys and connect with the SSH agent for fast, encrypted authentication.", + "description": "Two part message", + "example": "Store your keys and connect with the SSH agent for fast, encrypted authentication. Learn more about SSH agent" + }, + "newSshNudgeBodyTwo": { + "message": "Learn more about SSH agent", + "description": "Two part message", + "example": "Store your keys and connect with the SSH agent for fast, encrypted authentication. Learn more about SSH agent" + }, + "generatorNudgeTitle": { + "message": "Quickly create passwords" + }, + "generatorNudgeBodyOne": { + "message": "Easily create strong and unique passwords by clicking on", + "description": "Two part message", + "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." + }, + "generatorNudgeBodyTwo": { + "message": "to help you keep your logins secure.", + "description": "Two part message", + "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." + }, + "generatorNudgeBodyAria": { + "message": "Easily create strong and unique passwords by clicking on the Generate password button to help you keep your logins secure.", + "description": "Aria label for the body content of the generator nudge" + }, + "noPermissionsViewPage": { + "message": "You do not have permissions to view this page. Try logging in with a different account." } } diff --git a/apps/browser/src/_locales/ne/messages.json b/apps/browser/src/_locales/ne/messages.json index 7bec167e1c6..032d8c89d49 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,55 @@ "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.", + "notificationNewItemAria": { + "message": "New Item, opens in new window", + "description": "Aria label for the new item button in notification bar confirmation message when error is prompted" + }, + "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": { - "content": "$1" + "label": { + "content": "$1", + "example": "Login" } - }, - "description": "Shown to user after login is updated." + } + }, + "notificationLoginSaveConfirmation": { + "message": "saved to Bitwarden.", + "description": "Shown to user after item is saved." + }, + "notificationLoginUpdatedConfirmation": { + "message": "updated in Bitwarden.", + "description": "Shown to user after item is updated." + }, + "selectItemAriaLabel": { + "message": "Select $ITEMTYPE$, $ITEMNAME$", + "description": "Used by screen readers. $1 is the item type (like vault or folder), $2 is the selected item name.", + "placeholders": { + "itemType": { + "content": "$1" + }, + "itemName": { + "content": "$2" + } + } }, "saveAsNewLoginAction": { "message": "Save as new login", @@ -1082,12 +1120,16 @@ "message": "Update login", "description": "Button text for updating an existing login entry." }, - "saveLoginPrompt": { - "message": "Save login?", + "unlockToSave": { + "message": "Unlock to save this login", + "description": "User prompt to take action in order to save the login they just entered." + }, + "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": { @@ -1323,8 +1365,8 @@ "featureUnavailable": { "message": "Feature unavailable" }, - "encryptionKeyMigrationRequired": { - "message": "Encryption key migration required. Please login through the web vault to update your encryption key." + "legacyEncryptionUnsupported": { + "message": "Legacy encryption is no longer supported. Please contact support to recover your account." }, "premiumMembership": { "message": "Premium membership" @@ -1557,6 +1599,24 @@ "autofillSuggestionsSectionTitle": { "message": "Autofill suggestions" }, + "autofillSpotlightTitle": { + "message": "Easily find autofill suggestions" + }, + "autofillSpotlightDesc": { + "message": "Turn off your browser's autofill settings, so they don't conflict with Bitwarden." + }, + "turnOffBrowserAutofill": { + "message": "Turn off $BROWSER$ autofill", + "placeholders": { + "browser": { + "content": "$1", + "example": "Chrome" + } + } + }, + "turnOffAutofill": { + "message": "Turn off autofill" + }, "showInlineMenuLabel": { "message": "Show autofill suggestions on form fields" }, @@ -2144,6 +2204,9 @@ "useThisPassword": { "message": "Use this password" }, + "useThisPassphrase": { + "message": "Use this passphrase" + }, "useThisUsername": { "message": "Use this username" }, @@ -2167,15 +2230,6 @@ "vaultTimeoutAction1": { "message": "Timeout action" }, - "newCustomizationOptionsCalloutTitle": { - "message": "New customization options" - }, - "newCustomizationOptionsCalloutContent": { - "message": "Customize your vault experience with quick copy actions, compact mode, and more!" - }, - "newCustomizationOptionsCalloutLink": { - "message": "View all Appearance settings" - }, "lock": { "message": "Lock", "description": "Verb form: to make secure or inaccessible by" @@ -2623,6 +2677,10 @@ "message": "All Sends", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, + "maxAccessCountReached": { + "message": "Max access count reached", + "description": "This text will be displayed after a Send has been accessed the maximum amount of times." + }, "hideTextByDefault": { "message": "Hide text by default" }, @@ -2963,14 +3021,14 @@ "copyCustomFieldNameNotUnique": { "message": "No unique identifier found." }, - "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.", - "placeholders": { - "organization": { - "content": "$1", - "example": "My Org Name" - } - } + "removeMasterPasswordForOrganizationUserKeyConnector": { + "message": "A master password is no longer required for members of the following organization. Please confirm the domain below with your organization administrator." + }, + "organizationName": { + "message": "Organization name" + }, + "keyConnectorDomain": { + "message": "Key Connector domain" }, "leaveOrganization": { "message": "Leave organization" @@ -3532,12 +3590,36 @@ "deviceTrusted": { "message": "Device trusted" }, - "sendsNoItemsTitle": { - "message": "No active Sends", + "trustOrganization": { + "message": "Trust organization" + }, + "trust": { + "message": "Trust" + }, + "doNotTrust": { + "message": "Do not trust" + }, + "organizationNotTrusted": { + "message": "Organization is not trusted" + }, + "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" + }, + "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." + }, + "orgTrustWarning1": { + "message": "This organization has an Enterprise policy that will enroll you in account recovery. Enrollment will allow organization administrators to change your password. Only proceed if you recognize this organization and the fingerprint phrase displayed below matches the organization's fingerprint." + }, + "trustUser": { + "message": "Trust user" + }, + "sendsTitleNoItems": { + "message": "Send sensitive information safely", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, - "sendsNoItemsMessage": { - "message": "Use Send to securely share encrypted information with anyone.", + "sendsBodyNoItems": { + "message": "Share files and data securely with anyone, on any platform. Your information will remain end-to-end encrypted while limiting exposure.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "inputRequired": { @@ -4456,6 +4538,33 @@ } } }, + "downloadBitwarden": { + "message": "Download Bitwarden" + }, + "downloadBitwardenOnAllDevices": { + "message": "Download Bitwarden on all devices" + }, + "getTheMobileApp": { + "message": "Get the mobile app" + }, + "getTheMobileAppDesc": { + "message": "Access your passwords on the go with the Bitwarden mobile app." + }, + "getTheDesktopApp": { + "message": "Get the desktop app" + }, + "getTheDesktopAppDesc": { + "message": "Access your vault without a browser, then set up unlock with biometrics to expedite unlocking in both the desktop app and browser extension." + }, + "downloadFromBitwardenNow": { + "message": "Download from bitwarden.com now" + }, + "getItOnGooglePlay": { + "message": "Get it on Google Play" + }, + "downloadOnTheAppStore": { + "message": "Download on the App Store" + }, "permanentlyDeleteAttachmentConfirmation": { "message": "Are you sure you want to permanently delete this attachment?" }, @@ -4917,6 +5026,15 @@ "biometricsStatusHelptextUnavailableReasonUnknown": { "message": "Biometric unlock is currently unavailable for an unknown reason." }, + "unlockVault": { + "message": "Unlock your vault in seconds" + }, + "unlockVaultDesc": { + "message": "You can customize your unlock and timeout settings to more quickly access your vault." + }, + "unlockPinSet": { + "message": "Unlock PIN set" + }, "authenticating": { "message": "Authenticating" }, @@ -4928,8 +5046,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": { @@ -5079,42 +5197,6 @@ "beta": { "message": "Beta" }, - "importantNotice": { - "message": "Important notice" - }, - "setupTwoStepLogin": { - "message": "Set up two-step login" - }, - "newDeviceVerificationNoticeContentPage1": { - "message": "Bitwarden will send a code to your account email to verify logins from new devices starting in February 2025." - }, - "newDeviceVerificationNoticeContentPage2": { - "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" - }, - "newDeviceVerificationNoticePageOneFormContent": { - "message": "Do you have reliable access to your email, $EMAIL$?", - "placeholders": { - "email": { - "content": "$1", - "example": "your_name@email.com" - } - } - }, - "newDeviceVerificationNoticePageOneEmailAccessNo": { - "message": "No, I do not" - }, - "newDeviceVerificationNoticePageOneEmailAccessYes": { - "message": "Yes, I can reliably access my email" - }, - "turnOnTwoStepLogin": { - "message": "Turn on two-step login" - }, - "changeAcctEmail": { - "message": "Change account email" - }, "extensionWidth": { "message": "Extension width" }, @@ -5169,6 +5251,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 +5283,98 @@ }, "secureDevicesBody": { "message": "Save unlimited passwords across unlimited devices with Bitwarden mobile, browser, and desktop apps." + }, + "nudgeBadgeAria": { + "message": "1 notification" + }, + "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!" + }, + "hasItemsVaultNudgeBodyOne": { + "message": "Autofill items for the current page" + }, + "hasItemsVaultNudgeBodyTwo": { + "message": "Favorite items for easy access" + }, + "hasItemsVaultNudgeBodyThree": { + "message": "Search your vault for something else" + }, + "newLoginNudgeTitle": { + "message": "Save time with autofill" + }, + "newLoginNudgeBodyOne": { + "message": "Include a", + "description": "This is in multiple parts to allow for bold text in the middle of the sentence.", + "example": "Include a Website so this login appears as an autofill suggestion." + }, + "newLoginNudgeBodyBold": { + "message": "Website", + "description": "This is in multiple parts to allow for bold text in the middle of the sentence.", + "example": "Include a Website so this login appears as an autofill suggestion." + }, + "newLoginNudgeBodyTwo": { + "message": "so this login appears as an autofill suggestion.", + "description": "This is in multiple parts to allow for bold text in the middle of the sentence.", + "example": "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" + }, + "newSshNudgeBodyOne": { + "message": "Store your keys and connect with the SSH agent for fast, encrypted authentication.", + "description": "Two part message", + "example": "Store your keys and connect with the SSH agent for fast, encrypted authentication. Learn more about SSH agent" + }, + "newSshNudgeBodyTwo": { + "message": "Learn more about SSH agent", + "description": "Two part message", + "example": "Store your keys and connect with the SSH agent for fast, encrypted authentication. Learn more about SSH agent" + }, + "generatorNudgeTitle": { + "message": "Quickly create passwords" + }, + "generatorNudgeBodyOne": { + "message": "Easily create strong and unique passwords by clicking on", + "description": "Two part message", + "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." + }, + "generatorNudgeBodyTwo": { + "message": "to help you keep your logins secure.", + "description": "Two part message", + "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." + }, + "generatorNudgeBodyAria": { + "message": "Easily create strong and unique passwords by clicking on the Generate password button to help you keep your logins secure.", + "description": "Aria label for the body content of the generator nudge" + }, + "noPermissionsViewPage": { + "message": "You do not have permissions to view this page. Try logging in with a different account." } } diff --git a/apps/browser/src/_locales/nl/messages.json b/apps/browser/src/_locales/nl/messages.json index dd101e62e75..bad44058213 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)" @@ -465,7 +468,7 @@ "message": "Wachtwoord gegenereerd" }, "passphraseGenerated": { - "message": "Wachtwoorden gegenereerd" + "message": "Wachtwoordzin gegenereerd" }, "usernameGenerated": { "message": "Gebruikersnaam gegenereerd" @@ -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,55 @@ "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.", + "notificationNewItemAria": { + "message": "Nieuw Item, opent in nieuw venster", + "description": "Aria label for the new item button in notification bar confirmation message when error is prompted" + }, + "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": { - "content": "$1" + "label": { + "content": "$1", + "example": "Login" } - }, - "description": "Shown to user after login is updated." + } + }, + "notificationLoginSaveConfirmation": { + "message": "opslaan in Bitwarden.", + "description": "Shown to user after item is saved." + }, + "notificationLoginUpdatedConfirmation": { + "message": "bijgewerkt in Bitwarden.", + "description": "Shown to user after item is updated." + }, + "selectItemAriaLabel": { + "message": "$ITEMTYPE$, $ITEMNAME$ selecteren", + "description": "Used by screen readers. $1 is the item type (like vault or folder), $2 is the selected item name.", + "placeholders": { + "itemType": { + "content": "$1" + }, + "itemName": { + "content": "$2" + } + } }, "saveAsNewLoginAction": { "message": "Opslaan als nieuwe login", @@ -1082,12 +1120,16 @@ "message": "Login bijwerken", "description": "Button text for updating an existing login entry." }, - "saveLoginPrompt": { - "message": "Login opslaan?", + "unlockToSave": { + "message": "Ontgrendel voor het opslaan van deze login", + "description": "User prompt to take action in order to save the login they just entered." + }, + "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": { @@ -1323,8 +1365,8 @@ "featureUnavailable": { "message": "Functionaliteit niet beschikbaar" }, - "encryptionKeyMigrationRequired": { - "message": "Migratie van de encryptiesleutel vereist. Login via de website om je sleutel te bij te werken." + "legacyEncryptionUnsupported": { + "message": "Oude versleuteling wordt niet langer ondersteund. Neem contact op voor ondersteuning om je account te herstellen." }, "premiumMembership": { "message": "Premium-abonnement" @@ -1557,6 +1599,24 @@ "autofillSuggestionsSectionTitle": { "message": "Suggesties automatisch invullen" }, + "autofillSpotlightTitle": { + "message": "Gemakkelijk suggesties voor automatisch invullen vinden" + }, + "autofillSpotlightDesc": { + "message": "Schakel de autofill-instellingen van je browser uit, zodat ze niet conflicteren met Bitwarden." + }, + "turnOffBrowserAutofill": { + "message": "Automatisch invullen van $BROWSER$ uitschakelen", + "placeholders": { + "browser": { + "content": "$1", + "example": "Chrome" + } + } + }, + "turnOffAutofill": { + "message": "Automatisch invullen uitschakelen" + }, "showInlineMenuLabel": { "message": "Suggesties voor automatisch invullen op formuliervelden weergeven" }, @@ -2144,6 +2204,9 @@ "useThisPassword": { "message": "Dit wachtwoord gebruiken" }, + "useThisPassphrase": { + "message": "Deze wachtwoordzin gebruiken" + }, "useThisUsername": { "message": "Deze gebruikersnaam gebruiken" }, @@ -2167,15 +2230,6 @@ "vaultTimeoutAction1": { "message": "Time-out actie" }, - "newCustomizationOptionsCalloutTitle": { - "message": "Nieuwe aanpassingsopties" - }, - "newCustomizationOptionsCalloutContent": { - "message": "Pas je kluis ervaring aan met snelle kopieeracties, compacte modus en meer!" - }, - "newCustomizationOptionsCalloutLink": { - "message": "Alle weergave-instellingen bekijken" - }, "lock": { "message": "Vergrendelen", "description": "Verb form: to make secure or inaccessible by" @@ -2623,6 +2677,10 @@ "message": "Alle Sends", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, + "maxAccessCountReached": { + "message": "Maximum aantal keren benaderd", + "description": "This text will be displayed after a Send has been accessed the maximum amount of times." + }, "hideTextByDefault": { "message": "Tekst standaard verbergen" }, @@ -2963,14 +3021,14 @@ "copyCustomFieldNameNotUnique": { "message": "Geen unieke id gevonden." }, - "convertOrganizationEncryptionDesc": { - "message": "$ORGANIZATION$ gebruikt SSO met een zelf gehoste sleutelserver. Leden van deze organisatie kunnen inloggen zonder hoofdwachtwoord.", - "placeholders": { - "organization": { - "content": "$1", - "example": "My Org Name" - } - } + "removeMasterPasswordForOrganizationUserKeyConnector": { + "message": "A master password is no longer required for members of the following organization. Please confirm the domain below with your organization administrator." + }, + "organizationName": { + "message": "Organization name" + }, + "keyConnectorDomain": { + "message": "Key Connector domain" }, "leaveOrganization": { "message": "Organisatie verlaten" @@ -3532,12 +3590,36 @@ "deviceTrusted": { "message": "Vertrouwd apparaat" }, - "sendsNoItemsTitle": { - "message": "Geen actieve Sends", + "trustOrganization": { + "message": "Organisatie vertrouwen" + }, + "trust": { + "message": "Vertrouwen" + }, + "doNotTrust": { + "message": "Niet vertrouwen" + }, + "organizationNotTrusted": { + "message": "Organisatie is niet vertrouwd" + }, + "emergencyAccessTrustWarning": { + "message": "Bevestig, voor de veiligheid van je account, alleen als je noodtoegang hebt verleend aan deze gebruiker en de vingerafdruk voldoet aan wat er in hun account wordt weergegeven" + }, + "orgTrustWarning": { + "message": "Ga, voor de veiligheid van je account, alleen verder als je lid bent van deze organisatie, accountherstel hebt ingeschakeld en de hieronder weergegeven vingerafdruk overeenkomt met de vingerafdruk van de organisatie." + }, + "orgTrustWarning1": { + "message": "Deze organisatie heeft een Enterprise-beleid dat je inschrijft voor accountherstel. Inschrijving stelt beheerders van de organisatie in staat om je wachtwoord te wijzigen. Ga alleen verder als je deze organisatie herkent en de vingerafdrukzin die hieronder wordt weergegeven overeenkomt met de vingerafdruk van de organisatie." + }, + "trustUser": { + "message": "Gebruiker vertrouwen" + }, + "sendsTitleNoItems": { + "message": "Gevoelige informatie veilig versturen", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, - "sendsNoItemsMessage": { - "message": "Gebruik Send voor het veilig delen van versleutelde informatie met wie dan ook.", + "sendsBodyNoItems": { + "message": "Deel bestanden en gegevens veilig met iedereen, op elk platform. Je informatie blijft end-to-end versleuteld terwijl en blootstelling beperkt.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "inputRequired": { @@ -4456,6 +4538,33 @@ } } }, + "downloadBitwarden": { + "message": "Bitwarden downloaden" + }, + "downloadBitwardenOnAllDevices": { + "message": "Bitwarden op alle apparaten downloaden" + }, + "getTheMobileApp": { + "message": "De mobiele app downloaden" + }, + "getTheMobileAppDesc": { + "message": "Toegang tot je wachtwoorden onderweg met de mobiele Bitwarden-app." + }, + "getTheDesktopApp": { + "message": "De desktop-app downloaden" + }, + "getTheDesktopAppDesc": { + "message": "Toegang tot je kluis zonder browser, vervolgens ontgrendelen met biometrische gegevens om sneller te ontgrendelen in zowel de desktopapp als browserextensie." + }, + "downloadFromBitwardenNow": { + "message": "Nu van bitwarden.com downloaden" + }, + "getItOnGooglePlay": { + "message": "Download op Google Play" + }, + "downloadOnTheAppStore": { + "message": "Download on the App Store" + }, "permanentlyDeleteAttachmentConfirmation": { "message": "Weet je zeker dat je deze bijlage definitief wilt verwijderen?" }, @@ -4506,7 +4615,7 @@ "description": "Used within the inline menu to provide an aria description when users are attempting to fill a card cipher." }, "loginCredentials": { - "message": "Login referenties" + "message": "Inloggegevens" }, "authenticatorKey": { "message": "Authenticatiesleutel" @@ -4917,6 +5026,15 @@ "biometricsStatusHelptextUnavailableReasonUnknown": { "message": "Biometrisch ontgrendelen is momenteel niet beschikbaar om een onbekende reden." }, + "unlockVault": { + "message": "Ontgrendel je kluis in seconden" + }, + "unlockVaultDesc": { + "message": "Je kunt de instellingen voor ontgrendelen en time-out aanpassen om sneller toegang te krijgen tot je kluis." + }, + "unlockPinSet": { + "message": "PIN-code ontgrendelen instellen" + }, "authenticating": { "message": "Aan het inloggen" }, @@ -4928,8 +5046,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": { @@ -5079,42 +5197,6 @@ "beta": { "message": "Beta" }, - "importantNotice": { - "message": "Belangrijke mededeling" - }, - "setupTwoStepLogin": { - "message": "Tweestapsaanmelding instellen" - }, - "newDeviceVerificationNoticeContentPage1": { - "message": "Vanaf februari 2025 stuurt Bitwarden een code naar het e-mailadres van je account om inloggen op nieuwe apparaten te verifiëren." - }, - "newDeviceVerificationNoticeContentPage2": { - "message": "Je kunt tweestapsaanmelding instellen als een alternatieve manier om je account te beschermen of je e-mailadres te veranderen naar een waar je toegang toe hebt." - }, - "remindMeLater": { - "message": "Herinner me later" - }, - "newDeviceVerificationNoticePageOneFormContent": { - "message": "Heb je betrouwbare toegang tot je e-mail, $EMAIL$?", - "placeholders": { - "email": { - "content": "$1", - "example": "your_name@email.com" - } - } - }, - "newDeviceVerificationNoticePageOneEmailAccessNo": { - "message": "Nee, dat heb ik niet" - }, - "newDeviceVerificationNoticePageOneEmailAccessYes": { - "message": "Ja, ik heb betrouwbare toegang tot mijn e-mail" - }, - "turnOnTwoStepLogin": { - "message": "Tweestapsaanmelding inschakelen" - }, - "changeAcctEmail": { - "message": "E-mailadres van het account veranderen" - }, "extensionWidth": { "message": "Extensiebreedte" }, @@ -5169,6 +5251,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 +5283,98 @@ }, "secureDevicesBody": { "message": "Onbeperkt wachtwoorden opslaan op alle apparaten met Bitwarden-apps voor mobiel, browser en desktop." + }, + "nudgeBadgeAria": { + "message": "1 melding" + }, + "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!" + }, + "hasItemsVaultNudgeBodyOne": { + "message": "Automatisch invullen van items voor de huidige pagina" + }, + "hasItemsVaultNudgeBodyTwo": { + "message": "Items als favoriet markeren voor gemakkelijke toegang" + }, + "hasItemsVaultNudgeBodyThree": { + "message": "Je kluis doorzoeken naar iets anders" + }, + "newLoginNudgeTitle": { + "message": "Tijd besparen met automatisch aanvullen" + }, + "newLoginNudgeBodyOne": { + "message": "Voeg een", + "description": "This is in multiple parts to allow for bold text in the middle of the sentence.", + "example": "Include a Website so this login appears as an autofill suggestion." + }, + "newLoginNudgeBodyBold": { + "message": "Website", + "description": "This is in multiple parts to allow for bold text in the middle of the sentence.", + "example": "Include a Website so this login appears as an autofill suggestion." + }, + "newLoginNudgeBodyTwo": { + "message": "toe zodat deze login wordt weergegeven als een automatische invulsuggestie.", + "description": "This is in multiple parts to allow for bold text in the middle of the sentence.", + "example": "Include a Website so this login appears as an autofill suggestion." + }, + "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" + }, + "newSshNudgeBodyOne": { + "message": "Sla je sleutels op en verbind met de SSH-agent voor snelle, versleutelde authenticatie.", + "description": "Two part message", + "example": "Store your keys and connect with the SSH agent for fast, encrypted authentication. Learn more about SSH agent" + }, + "newSshNudgeBodyTwo": { + "message": "Meer informatie over SSH-agent", + "description": "Two part message", + "example": "Store your keys and connect with the SSH agent for fast, encrypted authentication. Learn more about SSH agent" + }, + "generatorNudgeTitle": { + "message": "Snel wachtwoorden maken" + }, + "generatorNudgeBodyOne": { + "message": "Maak eenvoudig sterke en unieke wachtwoorden door te klikken op", + "description": "Two part message", + "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." + }, + "generatorNudgeBodyTwo": { + "message": "om je te helpen je inloggegevens veilig te houden.", + "description": "Two part message", + "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." + }, + "generatorNudgeBodyAria": { + "message": "Maak eenvoudig sterke en unieke wachtwoorden door op de knop Wachtwoord genereren te klikken om je logins veilig te houden.", + "description": "Aria label for the body content of the generator nudge" + }, + "noPermissionsViewPage": { + "message": "Je hebt geen rechten om deze pagina te bekijken. Probeer in te loggen met een ander account." } } diff --git a/apps/browser/src/_locales/nn/messages.json b/apps/browser/src/_locales/nn/messages.json index 7bec167e1c6..032d8c89d49 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,55 @@ "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.", + "notificationNewItemAria": { + "message": "New Item, opens in new window", + "description": "Aria label for the new item button in notification bar confirmation message when error is prompted" + }, + "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": { - "content": "$1" + "label": { + "content": "$1", + "example": "Login" } - }, - "description": "Shown to user after login is updated." + } + }, + "notificationLoginSaveConfirmation": { + "message": "saved to Bitwarden.", + "description": "Shown to user after item is saved." + }, + "notificationLoginUpdatedConfirmation": { + "message": "updated in Bitwarden.", + "description": "Shown to user after item is updated." + }, + "selectItemAriaLabel": { + "message": "Select $ITEMTYPE$, $ITEMNAME$", + "description": "Used by screen readers. $1 is the item type (like vault or folder), $2 is the selected item name.", + "placeholders": { + "itemType": { + "content": "$1" + }, + "itemName": { + "content": "$2" + } + } }, "saveAsNewLoginAction": { "message": "Save as new login", @@ -1082,12 +1120,16 @@ "message": "Update login", "description": "Button text for updating an existing login entry." }, - "saveLoginPrompt": { - "message": "Save login?", + "unlockToSave": { + "message": "Unlock to save this login", + "description": "User prompt to take action in order to save the login they just entered." + }, + "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": { @@ -1323,8 +1365,8 @@ "featureUnavailable": { "message": "Feature unavailable" }, - "encryptionKeyMigrationRequired": { - "message": "Encryption key migration required. Please login through the web vault to update your encryption key." + "legacyEncryptionUnsupported": { + "message": "Legacy encryption is no longer supported. Please contact support to recover your account." }, "premiumMembership": { "message": "Premium membership" @@ -1557,6 +1599,24 @@ "autofillSuggestionsSectionTitle": { "message": "Autofill suggestions" }, + "autofillSpotlightTitle": { + "message": "Easily find autofill suggestions" + }, + "autofillSpotlightDesc": { + "message": "Turn off your browser's autofill settings, so they don't conflict with Bitwarden." + }, + "turnOffBrowserAutofill": { + "message": "Turn off $BROWSER$ autofill", + "placeholders": { + "browser": { + "content": "$1", + "example": "Chrome" + } + } + }, + "turnOffAutofill": { + "message": "Turn off autofill" + }, "showInlineMenuLabel": { "message": "Show autofill suggestions on form fields" }, @@ -2144,6 +2204,9 @@ "useThisPassword": { "message": "Use this password" }, + "useThisPassphrase": { + "message": "Use this passphrase" + }, "useThisUsername": { "message": "Use this username" }, @@ -2167,15 +2230,6 @@ "vaultTimeoutAction1": { "message": "Timeout action" }, - "newCustomizationOptionsCalloutTitle": { - "message": "New customization options" - }, - "newCustomizationOptionsCalloutContent": { - "message": "Customize your vault experience with quick copy actions, compact mode, and more!" - }, - "newCustomizationOptionsCalloutLink": { - "message": "View all Appearance settings" - }, "lock": { "message": "Lock", "description": "Verb form: to make secure or inaccessible by" @@ -2623,6 +2677,10 @@ "message": "All Sends", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, + "maxAccessCountReached": { + "message": "Max access count reached", + "description": "This text will be displayed after a Send has been accessed the maximum amount of times." + }, "hideTextByDefault": { "message": "Hide text by default" }, @@ -2963,14 +3021,14 @@ "copyCustomFieldNameNotUnique": { "message": "No unique identifier found." }, - "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.", - "placeholders": { - "organization": { - "content": "$1", - "example": "My Org Name" - } - } + "removeMasterPasswordForOrganizationUserKeyConnector": { + "message": "A master password is no longer required for members of the following organization. Please confirm the domain below with your organization administrator." + }, + "organizationName": { + "message": "Organization name" + }, + "keyConnectorDomain": { + "message": "Key Connector domain" }, "leaveOrganization": { "message": "Leave organization" @@ -3532,12 +3590,36 @@ "deviceTrusted": { "message": "Device trusted" }, - "sendsNoItemsTitle": { - "message": "No active Sends", + "trustOrganization": { + "message": "Trust organization" + }, + "trust": { + "message": "Trust" + }, + "doNotTrust": { + "message": "Do not trust" + }, + "organizationNotTrusted": { + "message": "Organization is not trusted" + }, + "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" + }, + "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." + }, + "orgTrustWarning1": { + "message": "This organization has an Enterprise policy that will enroll you in account recovery. Enrollment will allow organization administrators to change your password. Only proceed if you recognize this organization and the fingerprint phrase displayed below matches the organization's fingerprint." + }, + "trustUser": { + "message": "Trust user" + }, + "sendsTitleNoItems": { + "message": "Send sensitive information safely", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, - "sendsNoItemsMessage": { - "message": "Use Send to securely share encrypted information with anyone.", + "sendsBodyNoItems": { + "message": "Share files and data securely with anyone, on any platform. Your information will remain end-to-end encrypted while limiting exposure.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "inputRequired": { @@ -4456,6 +4538,33 @@ } } }, + "downloadBitwarden": { + "message": "Download Bitwarden" + }, + "downloadBitwardenOnAllDevices": { + "message": "Download Bitwarden on all devices" + }, + "getTheMobileApp": { + "message": "Get the mobile app" + }, + "getTheMobileAppDesc": { + "message": "Access your passwords on the go with the Bitwarden mobile app." + }, + "getTheDesktopApp": { + "message": "Get the desktop app" + }, + "getTheDesktopAppDesc": { + "message": "Access your vault without a browser, then set up unlock with biometrics to expedite unlocking in both the desktop app and browser extension." + }, + "downloadFromBitwardenNow": { + "message": "Download from bitwarden.com now" + }, + "getItOnGooglePlay": { + "message": "Get it on Google Play" + }, + "downloadOnTheAppStore": { + "message": "Download on the App Store" + }, "permanentlyDeleteAttachmentConfirmation": { "message": "Are you sure you want to permanently delete this attachment?" }, @@ -4917,6 +5026,15 @@ "biometricsStatusHelptextUnavailableReasonUnknown": { "message": "Biometric unlock is currently unavailable for an unknown reason." }, + "unlockVault": { + "message": "Unlock your vault in seconds" + }, + "unlockVaultDesc": { + "message": "You can customize your unlock and timeout settings to more quickly access your vault." + }, + "unlockPinSet": { + "message": "Unlock PIN set" + }, "authenticating": { "message": "Authenticating" }, @@ -4928,8 +5046,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": { @@ -5079,42 +5197,6 @@ "beta": { "message": "Beta" }, - "importantNotice": { - "message": "Important notice" - }, - "setupTwoStepLogin": { - "message": "Set up two-step login" - }, - "newDeviceVerificationNoticeContentPage1": { - "message": "Bitwarden will send a code to your account email to verify logins from new devices starting in February 2025." - }, - "newDeviceVerificationNoticeContentPage2": { - "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" - }, - "newDeviceVerificationNoticePageOneFormContent": { - "message": "Do you have reliable access to your email, $EMAIL$?", - "placeholders": { - "email": { - "content": "$1", - "example": "your_name@email.com" - } - } - }, - "newDeviceVerificationNoticePageOneEmailAccessNo": { - "message": "No, I do not" - }, - "newDeviceVerificationNoticePageOneEmailAccessYes": { - "message": "Yes, I can reliably access my email" - }, - "turnOnTwoStepLogin": { - "message": "Turn on two-step login" - }, - "changeAcctEmail": { - "message": "Change account email" - }, "extensionWidth": { "message": "Extension width" }, @@ -5169,6 +5251,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 +5283,98 @@ }, "secureDevicesBody": { "message": "Save unlimited passwords across unlimited devices with Bitwarden mobile, browser, and desktop apps." + }, + "nudgeBadgeAria": { + "message": "1 notification" + }, + "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!" + }, + "hasItemsVaultNudgeBodyOne": { + "message": "Autofill items for the current page" + }, + "hasItemsVaultNudgeBodyTwo": { + "message": "Favorite items for easy access" + }, + "hasItemsVaultNudgeBodyThree": { + "message": "Search your vault for something else" + }, + "newLoginNudgeTitle": { + "message": "Save time with autofill" + }, + "newLoginNudgeBodyOne": { + "message": "Include a", + "description": "This is in multiple parts to allow for bold text in the middle of the sentence.", + "example": "Include a Website so this login appears as an autofill suggestion." + }, + "newLoginNudgeBodyBold": { + "message": "Website", + "description": "This is in multiple parts to allow for bold text in the middle of the sentence.", + "example": "Include a Website so this login appears as an autofill suggestion." + }, + "newLoginNudgeBodyTwo": { + "message": "so this login appears as an autofill suggestion.", + "description": "This is in multiple parts to allow for bold text in the middle of the sentence.", + "example": "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" + }, + "newSshNudgeBodyOne": { + "message": "Store your keys and connect with the SSH agent for fast, encrypted authentication.", + "description": "Two part message", + "example": "Store your keys and connect with the SSH agent for fast, encrypted authentication. Learn more about SSH agent" + }, + "newSshNudgeBodyTwo": { + "message": "Learn more about SSH agent", + "description": "Two part message", + "example": "Store your keys and connect with the SSH agent for fast, encrypted authentication. Learn more about SSH agent" + }, + "generatorNudgeTitle": { + "message": "Quickly create passwords" + }, + "generatorNudgeBodyOne": { + "message": "Easily create strong and unique passwords by clicking on", + "description": "Two part message", + "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." + }, + "generatorNudgeBodyTwo": { + "message": "to help you keep your logins secure.", + "description": "Two part message", + "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." + }, + "generatorNudgeBodyAria": { + "message": "Easily create strong and unique passwords by clicking on the Generate password button to help you keep your logins secure.", + "description": "Aria label for the body content of the generator nudge" + }, + "noPermissionsViewPage": { + "message": "You do not have permissions to view this page. Try logging in with a different account." } } diff --git a/apps/browser/src/_locales/or/messages.json b/apps/browser/src/_locales/or/messages.json index 7bec167e1c6..032d8c89d49 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,55 @@ "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.", + "notificationNewItemAria": { + "message": "New Item, opens in new window", + "description": "Aria label for the new item button in notification bar confirmation message when error is prompted" + }, + "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": { - "content": "$1" + "label": { + "content": "$1", + "example": "Login" } - }, - "description": "Shown to user after login is updated." + } + }, + "notificationLoginSaveConfirmation": { + "message": "saved to Bitwarden.", + "description": "Shown to user after item is saved." + }, + "notificationLoginUpdatedConfirmation": { + "message": "updated in Bitwarden.", + "description": "Shown to user after item is updated." + }, + "selectItemAriaLabel": { + "message": "Select $ITEMTYPE$, $ITEMNAME$", + "description": "Used by screen readers. $1 is the item type (like vault or folder), $2 is the selected item name.", + "placeholders": { + "itemType": { + "content": "$1" + }, + "itemName": { + "content": "$2" + } + } }, "saveAsNewLoginAction": { "message": "Save as new login", @@ -1082,12 +1120,16 @@ "message": "Update login", "description": "Button text for updating an existing login entry." }, - "saveLoginPrompt": { - "message": "Save login?", + "unlockToSave": { + "message": "Unlock to save this login", + "description": "User prompt to take action in order to save the login they just entered." + }, + "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": { @@ -1323,8 +1365,8 @@ "featureUnavailable": { "message": "Feature unavailable" }, - "encryptionKeyMigrationRequired": { - "message": "Encryption key migration required. Please login through the web vault to update your encryption key." + "legacyEncryptionUnsupported": { + "message": "Legacy encryption is no longer supported. Please contact support to recover your account." }, "premiumMembership": { "message": "Premium membership" @@ -1557,6 +1599,24 @@ "autofillSuggestionsSectionTitle": { "message": "Autofill suggestions" }, + "autofillSpotlightTitle": { + "message": "Easily find autofill suggestions" + }, + "autofillSpotlightDesc": { + "message": "Turn off your browser's autofill settings, so they don't conflict with Bitwarden." + }, + "turnOffBrowserAutofill": { + "message": "Turn off $BROWSER$ autofill", + "placeholders": { + "browser": { + "content": "$1", + "example": "Chrome" + } + } + }, + "turnOffAutofill": { + "message": "Turn off autofill" + }, "showInlineMenuLabel": { "message": "Show autofill suggestions on form fields" }, @@ -2144,6 +2204,9 @@ "useThisPassword": { "message": "Use this password" }, + "useThisPassphrase": { + "message": "Use this passphrase" + }, "useThisUsername": { "message": "Use this username" }, @@ -2167,15 +2230,6 @@ "vaultTimeoutAction1": { "message": "Timeout action" }, - "newCustomizationOptionsCalloutTitle": { - "message": "New customization options" - }, - "newCustomizationOptionsCalloutContent": { - "message": "Customize your vault experience with quick copy actions, compact mode, and more!" - }, - "newCustomizationOptionsCalloutLink": { - "message": "View all Appearance settings" - }, "lock": { "message": "Lock", "description": "Verb form: to make secure or inaccessible by" @@ -2623,6 +2677,10 @@ "message": "All Sends", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, + "maxAccessCountReached": { + "message": "Max access count reached", + "description": "This text will be displayed after a Send has been accessed the maximum amount of times." + }, "hideTextByDefault": { "message": "Hide text by default" }, @@ -2963,14 +3021,14 @@ "copyCustomFieldNameNotUnique": { "message": "No unique identifier found." }, - "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.", - "placeholders": { - "organization": { - "content": "$1", - "example": "My Org Name" - } - } + "removeMasterPasswordForOrganizationUserKeyConnector": { + "message": "A master password is no longer required for members of the following organization. Please confirm the domain below with your organization administrator." + }, + "organizationName": { + "message": "Organization name" + }, + "keyConnectorDomain": { + "message": "Key Connector domain" }, "leaveOrganization": { "message": "Leave organization" @@ -3532,12 +3590,36 @@ "deviceTrusted": { "message": "Device trusted" }, - "sendsNoItemsTitle": { - "message": "No active Sends", + "trustOrganization": { + "message": "Trust organization" + }, + "trust": { + "message": "Trust" + }, + "doNotTrust": { + "message": "Do not trust" + }, + "organizationNotTrusted": { + "message": "Organization is not trusted" + }, + "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" + }, + "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." + }, + "orgTrustWarning1": { + "message": "This organization has an Enterprise policy that will enroll you in account recovery. Enrollment will allow organization administrators to change your password. Only proceed if you recognize this organization and the fingerprint phrase displayed below matches the organization's fingerprint." + }, + "trustUser": { + "message": "Trust user" + }, + "sendsTitleNoItems": { + "message": "Send sensitive information safely", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, - "sendsNoItemsMessage": { - "message": "Use Send to securely share encrypted information with anyone.", + "sendsBodyNoItems": { + "message": "Share files and data securely with anyone, on any platform. Your information will remain end-to-end encrypted while limiting exposure.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "inputRequired": { @@ -4456,6 +4538,33 @@ } } }, + "downloadBitwarden": { + "message": "Download Bitwarden" + }, + "downloadBitwardenOnAllDevices": { + "message": "Download Bitwarden on all devices" + }, + "getTheMobileApp": { + "message": "Get the mobile app" + }, + "getTheMobileAppDesc": { + "message": "Access your passwords on the go with the Bitwarden mobile app." + }, + "getTheDesktopApp": { + "message": "Get the desktop app" + }, + "getTheDesktopAppDesc": { + "message": "Access your vault without a browser, then set up unlock with biometrics to expedite unlocking in both the desktop app and browser extension." + }, + "downloadFromBitwardenNow": { + "message": "Download from bitwarden.com now" + }, + "getItOnGooglePlay": { + "message": "Get it on Google Play" + }, + "downloadOnTheAppStore": { + "message": "Download on the App Store" + }, "permanentlyDeleteAttachmentConfirmation": { "message": "Are you sure you want to permanently delete this attachment?" }, @@ -4917,6 +5026,15 @@ "biometricsStatusHelptextUnavailableReasonUnknown": { "message": "Biometric unlock is currently unavailable for an unknown reason." }, + "unlockVault": { + "message": "Unlock your vault in seconds" + }, + "unlockVaultDesc": { + "message": "You can customize your unlock and timeout settings to more quickly access your vault." + }, + "unlockPinSet": { + "message": "Unlock PIN set" + }, "authenticating": { "message": "Authenticating" }, @@ -4928,8 +5046,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": { @@ -5079,42 +5197,6 @@ "beta": { "message": "Beta" }, - "importantNotice": { - "message": "Important notice" - }, - "setupTwoStepLogin": { - "message": "Set up two-step login" - }, - "newDeviceVerificationNoticeContentPage1": { - "message": "Bitwarden will send a code to your account email to verify logins from new devices starting in February 2025." - }, - "newDeviceVerificationNoticeContentPage2": { - "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" - }, - "newDeviceVerificationNoticePageOneFormContent": { - "message": "Do you have reliable access to your email, $EMAIL$?", - "placeholders": { - "email": { - "content": "$1", - "example": "your_name@email.com" - } - } - }, - "newDeviceVerificationNoticePageOneEmailAccessNo": { - "message": "No, I do not" - }, - "newDeviceVerificationNoticePageOneEmailAccessYes": { - "message": "Yes, I can reliably access my email" - }, - "turnOnTwoStepLogin": { - "message": "Turn on two-step login" - }, - "changeAcctEmail": { - "message": "Change account email" - }, "extensionWidth": { "message": "Extension width" }, @@ -5169,6 +5251,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 +5283,98 @@ }, "secureDevicesBody": { "message": "Save unlimited passwords across unlimited devices with Bitwarden mobile, browser, and desktop apps." + }, + "nudgeBadgeAria": { + "message": "1 notification" + }, + "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!" + }, + "hasItemsVaultNudgeBodyOne": { + "message": "Autofill items for the current page" + }, + "hasItemsVaultNudgeBodyTwo": { + "message": "Favorite items for easy access" + }, + "hasItemsVaultNudgeBodyThree": { + "message": "Search your vault for something else" + }, + "newLoginNudgeTitle": { + "message": "Save time with autofill" + }, + "newLoginNudgeBodyOne": { + "message": "Include a", + "description": "This is in multiple parts to allow for bold text in the middle of the sentence.", + "example": "Include a Website so this login appears as an autofill suggestion." + }, + "newLoginNudgeBodyBold": { + "message": "Website", + "description": "This is in multiple parts to allow for bold text in the middle of the sentence.", + "example": "Include a Website so this login appears as an autofill suggestion." + }, + "newLoginNudgeBodyTwo": { + "message": "so this login appears as an autofill suggestion.", + "description": "This is in multiple parts to allow for bold text in the middle of the sentence.", + "example": "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" + }, + "newSshNudgeBodyOne": { + "message": "Store your keys and connect with the SSH agent for fast, encrypted authentication.", + "description": "Two part message", + "example": "Store your keys and connect with the SSH agent for fast, encrypted authentication. Learn more about SSH agent" + }, + "newSshNudgeBodyTwo": { + "message": "Learn more about SSH agent", + "description": "Two part message", + "example": "Store your keys and connect with the SSH agent for fast, encrypted authentication. Learn more about SSH agent" + }, + "generatorNudgeTitle": { + "message": "Quickly create passwords" + }, + "generatorNudgeBodyOne": { + "message": "Easily create strong and unique passwords by clicking on", + "description": "Two part message", + "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." + }, + "generatorNudgeBodyTwo": { + "message": "to help you keep your logins secure.", + "description": "Two part message", + "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." + }, + "generatorNudgeBodyAria": { + "message": "Easily create strong and unique passwords by clicking on the Generate password button to help you keep your logins secure.", + "description": "Aria label for the body content of the generator nudge" + }, + "noPermissionsViewPage": { + "message": "You do not have permissions to view this page. Try logging in with a different account." } } diff --git a/apps/browser/src/_locales/pl/messages.json b/apps/browser/src/_locales/pl/messages.json index 3d25e6f123c..df5ff1b4b37 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,55 @@ "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.", + "notificationNewItemAria": { + "message": "Nowy element, otwiera się w nowym oknie", + "description": "Aria label for the new item button in notification bar confirmation message when error is prompted" + }, + "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": { - "content": "$1" + "label": { + "content": "$1", + "example": "Login" } - }, - "description": "Shown to user after login is updated." + } + }, + "notificationLoginSaveConfirmation": { + "message": "zapisano w Bitwarden.", + "description": "Shown to user after item is saved." + }, + "notificationLoginUpdatedConfirmation": { + "message": "zaktualizowano w Bitwarden.", + "description": "Shown to user after item is updated." + }, + "selectItemAriaLabel": { + "message": "Wybierz $ITEMTYPE$, $ITEMNAME$", + "description": "Used by screen readers. $1 is the item type (like vault or folder), $2 is the selected item name.", + "placeholders": { + "itemType": { + "content": "$1" + }, + "itemName": { + "content": "$2" + } + } }, "saveAsNewLoginAction": { "message": "Zapisz jako nowy login", @@ -1082,11 +1120,15 @@ "message": "Zaktualizuj dane logowania", "description": "Button text for updating an existing login entry." }, - "saveLoginPrompt": { - "message": "Zapisać dane logowania?", + "unlockToSave": { + "message": "Odblokuj, aby zapisać ten login", + "description": "User prompt to take action in order to save the login they just entered." + }, + "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." }, @@ -1323,8 +1365,8 @@ "featureUnavailable": { "message": "Funkcja jest niedostępna" }, - "encryptionKeyMigrationRequired": { - "message": "Wymagana jest migracja klucza szyfrowania. Zaloguj się przez sejf internetowy, aby zaktualizować klucz szyfrowania." + "legacyEncryptionUnsupported": { + "message": "Starsze szyfrowanie nie jest już obsługiwane. Skontaktuj się z pomocą techniczną, aby odzyskać swoje konto." }, "premiumMembership": { "message": "Konto Premium" @@ -1557,6 +1599,24 @@ "autofillSuggestionsSectionTitle": { "message": "Sugestie autouzupełniania" }, + "autofillSpotlightTitle": { + "message": "Łatwe znajdowanie sugestii autouzupełniania" + }, + "autofillSpotlightDesc": { + "message": "Wyłącz ustawienia autouzupełniania swojej przeglądarki, aby nie kolidowały z Bitwarden." + }, + "turnOffBrowserAutofill": { + "message": "Wyłącz autouzupełnianie $BROWSER$", + "placeholders": { + "browser": { + "content": "$1", + "example": "Chrome" + } + } + }, + "turnOffAutofill": { + "message": "Wyłącz autouzupełnienie" + }, "showInlineMenuLabel": { "message": "Pokaż sugestie autouzupełniania na polach formularza" }, @@ -2144,6 +2204,9 @@ "useThisPassword": { "message": "Użyj tego hasła" }, + "useThisPassphrase": { + "message": "Użyj tego hasła wyrazowego" + }, "useThisUsername": { "message": "Użyj tej nazwy użytkownika" }, @@ -2167,15 +2230,6 @@ "vaultTimeoutAction1": { "message": "Akcja po przekroczeniu limitu czasu" }, - "newCustomizationOptionsCalloutTitle": { - "message": "Nowe opcje dostosowywania" - }, - "newCustomizationOptionsCalloutContent": { - "message": "Dostosuj swój sejf dzięki akcjom szybkiego kopiowania, trybowi kompaktowemu i więcej!" - }, - "newCustomizationOptionsCalloutLink": { - "message": "Zobacz wszystkie ustawienia wyglądu" - }, "lock": { "message": "Zablokuj", "description": "Verb form: to make secure or inaccessible by" @@ -2623,6 +2677,10 @@ "message": "Wszystkie wysyłki", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, + "maxAccessCountReached": { + "message": "Maksymalna liczba dostępów została osiągnięta", + "description": "This text will be displayed after a Send has been accessed the maximum amount of times." + }, "hideTextByDefault": { "message": "Domyślnie ukryj tekst" }, @@ -2963,14 +3021,14 @@ "copyCustomFieldNameNotUnique": { "message": "Nie znaleziono unikatowego identyfikatora." }, - "convertOrganizationEncryptionDesc": { - "message": "Organizacja $ORGANIZATION$ używa jednokrotnego logowania SSO z własnym serwerem kluczy. Użytkownicy nie muszą logować się za pomocą hasła głównego.", - "placeholders": { - "organization": { - "content": "$1", - "example": "My Org Name" - } - } + "removeMasterPasswordForOrganizationUserKeyConnector": { + "message": "Hasło główne nie jest już wymagane dla członków następującej organizacji. Proszę potwierdzić poniższą domenę u administratora organizacji." + }, + "organizationName": { + "message": "Nazwa organizacji" + }, + "keyConnectorDomain": { + "message": "Domena Key Connector'a" }, "leaveOrganization": { "message": "Opuść organizację" @@ -3532,12 +3590,36 @@ "deviceTrusted": { "message": "Zaufano urządzeniu" }, - "sendsNoItemsTitle": { - "message": "Brak aktywnych wysyłek", + "trustOrganization": { + "message": "Zaufaj organizacji" + }, + "trust": { + "message": "Zaufaj" + }, + "doNotTrust": { + "message": "Nie ufaj" + }, + "organizationNotTrusted": { + "message": "Organizacja nie jest zaufana" + }, + "emergencyAccessTrustWarning": { + "message": "Dla bezpieczeństwa Twojego konta potwierdź tylko, jeśli przyznano temu użytkownikowi dostęp awaryjny i jego odcisk palca pasuje do tego, co widnieje na jego koncie" + }, + "orgTrustWarning": { + "message": "Dla zapewnienia bezpieczeństwa konta kontynuuj tylko wtedy, gdy jesteś członkiem tej organizacji, włączono odzyskiwanie konta, a odcisk palca wyświetlany poniżej pasuje do odcisku palca organizacji." + }, + "orgTrustWarning1": { + "message": "Polityka korporacyjna tej organizacji umożliwia zapisanie Cię do programu odzyskiwania kont. Rejestracja umożliwi administratorom organizacji zmianę Twojego hasła. Możesz kontynuować tylko wtedy, gdy znasz tę organizację, a odcisk palca pokazany poniżej pasuje do odcisku palca tej organizacji." + }, + "trustUser": { + "message": "Zaufaj użytkownikowi" + }, + "sendsTitleNoItems": { + "message": "Wysyłaj bezpiecznie poufne informacje", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, - "sendsNoItemsMessage": { - "message": "Użyj wysyłki, aby bezpiecznie dzielić się zaszyfrowanymi informacjami ze wszystkimi.", + "sendsBodyNoItems": { + "message": "Udostępniaj pliki i dane bezpiecznie każdemu, na każdej platformie. Twoje dane pozostaną zaszyfrowane end-to-end przy jednoczesnym ograniczeniu narażenia.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "inputRequired": { @@ -4456,6 +4538,33 @@ } } }, + "downloadBitwarden": { + "message": "Pobierz Bitwarden" + }, + "downloadBitwardenOnAllDevices": { + "message": "Pobierz Bitwarden na wszystkich urządzeniach" + }, + "getTheMobileApp": { + "message": "Pobierz aplikację mobilną" + }, + "getTheMobileAppDesc": { + "message": "Uzyskaj dostęp do haseł przy pomocy aplikacji mobilnej Bitwarden." + }, + "getTheDesktopApp": { + "message": "Pobierz aplikację desktopową" + }, + "getTheDesktopAppDesc": { + "message": "Uzyskaj dostęp do sejfu bez przeglądarki, a następnie ustaw odblokowanie biometryczne, aby przyspieszyć odblokowanie zarówno w aplikacji desktopowej, jak i w rozszerzeniu przeglądarki." + }, + "downloadFromBitwardenNow": { + "message": "Pobierz teraz z bitwarden.com" + }, + "getItOnGooglePlay": { + "message": "Pobierz z Google Play" + }, + "downloadOnTheAppStore": { + "message": "Pobierz z App Store" + }, "permanentlyDeleteAttachmentConfirmation": { "message": "Czy na pewno chcesz trwale usunąć ten załącznik?" }, @@ -4917,6 +5026,15 @@ "biometricsStatusHelptextUnavailableReasonUnknown": { "message": "Odblokowanie biometryczne jest obecnie niedostępne z nieznanego powodu." }, + "unlockVault": { + "message": "Odblokuj swój sejf w kilka sekund" + }, + "unlockVaultDesc": { + "message": "Możesz dostosować ustawienia odblokowania i limitu czasu, aby szybciej uzyskać dostęp do sejfu." + }, + "unlockPinSet": { + "message": "Ustaw kod PIN odblokowujący" + }, "authenticating": { "message": "Uwierzytelnianie" }, @@ -4928,8 +5046,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": { @@ -5079,42 +5197,6 @@ "beta": { "message": "Beta" }, - "importantNotice": { - "message": "Ważna informacja" - }, - "setupTwoStepLogin": { - "message": "Skonfiguruj dwustopniowe logowanie" - }, - "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." - }, - "newDeviceVerificationNoticeContentPage2": { - "message": "Możesz skonfigurować dwustopniowe logowanie jako alternatywny sposób ochrony konta lub zmienić swój adres e-mail, do którego masz dostęp." - }, - "remindMeLater": { - "message": "Przypomnij mi później" - }, - "newDeviceVerificationNoticePageOneFormContent": { - "message": "Czy masz pewny dostęp do swojego adresu e-mail, $EMAIL$?", - "placeholders": { - "email": { - "content": "$1", - "example": "your_name@email.com" - } - } - }, - "newDeviceVerificationNoticePageOneEmailAccessNo": { - "message": "Nie, nie mam" - }, - "newDeviceVerificationNoticePageOneEmailAccessYes": { - "message": "Tak, mam pewny dostęp do mojego adresu e-mail" - }, - "turnOnTwoStepLogin": { - "message": "Włącz dwustopniowe logowanie" - }, - "changeAcctEmail": { - "message": "Zmień adres e-mail konta" - }, "extensionWidth": { "message": "Szerokość rozszerzenia" }, @@ -5169,6 +5251,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 +5283,98 @@ }, "secureDevicesBody": { "message": "Zapisuj nieograniczoną liczbę haseł na nieograniczonej liczbie urządzeń dzięki aplikacjom Bitwarden na urządzenia mobilne, przeglądarki i komputery stacjonarne." + }, + "nudgeBadgeAria": { + "message": "1 powiadomienie" + }, + "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!" + }, + "hasItemsVaultNudgeBodyOne": { + "message": "Autouzupełnianie elementów dla bieżącej strony" + }, + "hasItemsVaultNudgeBodyTwo": { + "message": "Ulubione elementy dla szybkiego dostępu" + }, + "hasItemsVaultNudgeBodyThree": { + "message": "Przeszukaj swój sejf w poszukiwaniu czegoś innego" + }, + "newLoginNudgeTitle": { + "message": "Oszczędzaj czas dzięki autouzupełnianiu" + }, + "newLoginNudgeBodyOne": { + "message": "Dołącz", + "description": "This is in multiple parts to allow for bold text in the middle of the sentence.", + "example": "Include a Website so this login appears as an autofill suggestion." + }, + "newLoginNudgeBodyBold": { + "message": "stronę internetową, ", + "description": "This is in multiple parts to allow for bold text in the middle of the sentence.", + "example": "Include a Website so this login appears as an autofill suggestion." + }, + "newLoginNudgeBodyTwo": { + "message": "aby ten login pojawił się jako sugestia autouzupełniania.", + "description": "This is in multiple parts to allow for bold text in the middle of the sentence.", + "example": "Include a Website so this login appears as an autofill suggestion." + }, + "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" + }, + "newSshNudgeBodyOne": { + "message": "Przechowuj swoje klucze i połącz się z agentem SSH dla szybkiego, szyfrowanego uwierzytelniania.", + "description": "Two part message", + "example": "Store your keys and connect with the SSH agent for fast, encrypted authentication. Learn more about SSH agent" + }, + "newSshNudgeBodyTwo": { + "message": "Dowiedz się więcej o agencie SSH", + "description": "Two part message", + "example": "Store your keys and connect with the SSH agent for fast, encrypted authentication. Learn more about SSH agent" + }, + "generatorNudgeTitle": { + "message": "Szybko twórz hasła" + }, + "generatorNudgeBodyOne": { + "message": "Łatwo twórz silne i unikalne hasła, klikając na", + "description": "Two part message", + "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." + }, + "generatorNudgeBodyTwo": { + "message": ", aby pomóc Ci zachować bezpieczeństwo Twoich danych logowania.", + "description": "Two part message", + "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." + }, + "generatorNudgeBodyAria": { + "message": "Łatwo twórz silne i unikalne hasła, klikając na Wygeneruj Hasło, aby pomóc Ci zachować bezpieczeństwo Twoich danych logowania.", + "description": "Aria label for the body content of the generator nudge" + }, + "noPermissionsViewPage": { + "message": "Nie masz uprawnień do przeglądania tej strony. Spróbuj zalogować się na inne konto." } } diff --git a/apps/browser/src/_locales/pt_BR/messages.json b/apps/browser/src/_locales/pt_BR/messages.json index 9d1744f5473..1ae2c2c1a07 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)" @@ -197,19 +200,19 @@ "message": "Autopreencher" }, "autoFillLogin": { - "message": "Preenchimento automático ‘login’" + "message": "Preencher login automaticamente" }, "autoFillCard": { - "message": "Preenchimento automático cartão" + "message": "Preencher cartão automaticamente" }, "autoFillIdentity": { - "message": "Preenchimento automático identidade" + "message": "Preencher identidade automaticamente" }, "fillVerificationCode": { "message": "Preencher o código de verificação" }, "fillVerificationCodeAria": { - "message": "Preencher o código de verificação", + "message": "Preencher código de verificação", "description": "Aria label for the heading displayed the inline menu for totp code autofill" }, "generatePasswordCopied": { @@ -827,7 +830,7 @@ } }, "autofillError": { - "message": "Não é possível auto-preencher o item selecionado nesta página. Em alternativa, copie e cole a informação." + "message": "Não é possível preencher automaticamente o item selecionado nesta página. Em vez disso, copie e cole a informação." }, "totpCaptureError": { "message": "Não foi possível escanear o código QR a partir da página atual" @@ -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" }, @@ -1019,7 +1025,7 @@ "message": "Pedir para adicionar um item se um não for encontrado no seu cofre. Aplica-se a todas as contas logadas." }, "showCardsInVaultViewV2": { - "message": "Sempre mostrar cartões como sugestões de preenchimento automático na Tela do Cofre" + "message": "Sempre mostrar cartões como sugestões de preenchimento automático na tela do Cofre" }, "showCardsCurrentTab": { "message": "Mostrar cartões em páginas com guias." @@ -1037,10 +1043,10 @@ "message": "Liste os itens de identidade na aba atual para facilitar preenchimento automático." }, "clickToAutofillOnVault": { - "message": "Clique em itens para autopreencher na Tela do Cofre" + "message": "Clique em itens na tela do Cofre para preencher automaticamente" }, "clickToAutofill": { - "message": "Selecione itens sugeridos pelo autopreenchimento" + "message": "Selecione o item para preenchê-lo automaticamente" }, "clearClipboard": { "message": "Limpar Área de Transferência", @@ -1056,23 +1062,55 @@ "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.", + "notificationNewItemAria": { + "message": "New Item, opens in new window", + "description": "Aria label for the new item button in notification bar confirmation message when error is prompted" + }, + "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": { - "content": "$1" + "label": { + "content": "$1", + "example": "Login" } - }, - "description": "Shown to user after login is updated." + } + }, + "notificationLoginSaveConfirmation": { + "message": "saved to Bitwarden.", + "description": "Shown to user after item is saved." + }, + "notificationLoginUpdatedConfirmation": { + "message": "updated in Bitwarden.", + "description": "Shown to user after item is updated." + }, + "selectItemAriaLabel": { + "message": "Select $ITEMTYPE$, $ITEMNAME$", + "description": "Used by screen readers. $1 is the item type (like vault or folder), $2 is the selected item name.", + "placeholders": { + "itemType": { + "content": "$1" + }, + "itemName": { + "content": "$2" + } + } }, "saveAsNewLoginAction": { "message": "Salvar como nova sessão", @@ -1082,12 +1120,16 @@ "message": "Atualizar sessão", "description": "Button text for updating an existing login entry." }, - "saveLoginPrompt": { - "message": "Salvar sessão?", + "unlockToSave": { + "message": "Unlock to save this login", + "description": "User prompt to take action in order to save the login they just entered." + }, + "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": { @@ -1153,7 +1195,7 @@ "message": "Atualizar" }, "notificationUnlockDesc": { - "message": "Desbloqueie o seu cofre do Bitwarden para concluir a solicitação de autopreenchimento." + "message": "Desbloqueie o seu cofre do Bitwarden para concluir a solicitação de preenchimento automático." }, "notificationUnlock": { "message": "Desbloquear" @@ -1171,7 +1213,7 @@ "message": "Use um clique secundário para acessar a geração de senha e os logins correspondentes para o site. Aplica-se a todas as contas logadas." }, "defaultUriMatchDetection": { - "message": "Detecção de Correspondência de URI Padrão", + "message": "Detecção de correspondência de URI padrão", "description": "Default URI match detection for autofill." }, "defaultUriMatchDetectionDesc": { @@ -1323,8 +1365,8 @@ "featureUnavailable": { "message": "Funcionalidade Indisponível" }, - "encryptionKeyMigrationRequired": { - "message": "É necessário migrar sua chave de criptografia. Faça login através do cofre web para atualizar sua chave de criptografia." + "legacyEncryptionUnsupported": { + "message": "Legacy encryption is no longer supported. Please contact support to recover your account." }, "premiumMembership": { "message": "Assinatura Premium" @@ -1557,6 +1599,24 @@ "autofillSuggestionsSectionTitle": { "message": "Sugestões de preenchimento automático" }, + "autofillSpotlightTitle": { + "message": "Easily find autofill suggestions" + }, + "autofillSpotlightDesc": { + "message": "Turn off your browser's autofill settings, so they don't conflict with Bitwarden." + }, + "turnOffBrowserAutofill": { + "message": "Turn off $BROWSER$ autofill", + "placeholders": { + "browser": { + "content": "$1", + "example": "Chrome" + } + } + }, + "turnOffAutofill": { + "message": "Turn off autofill" + }, "showInlineMenuLabel": { "message": "Mostrar sugestões de preenchimento automático nos campos de formulários" }, @@ -1594,7 +1654,7 @@ "message": "Preenchimento automático ao carregar a página" }, "enableAutoFillOnPageLoad": { - "message": "Auto-preencher ao carregar a página" + "message": "Preencher automaticamente ao carregar a página" }, "enableAutoFillOnPageLoadDesc": { "message": "Se um formulário de login for detectado, realizar automaticamente um auto-preenchimento quando a página web carregar." @@ -1612,19 +1672,19 @@ "message": "Configuração de autopreenchimento padrão para itens de credenciais" }, "defaultAutoFillOnPageLoadDesc": { - "message": "Depois de habilitar o Auto-preenchimento ao carregar a página, você pode habilitar ou desabilitar o recurso para itens de credenciais individuais. Esta é a configuração padrão para itens de credenciais que não são configurados separadamente." + "message": "Você pode desativar o preenchimento automático no carregamento da página para credenciais individuais na tela de Editar do item." }, "itemAutoFillOnPageLoad": { - "message": "Auto-preencher no Carregamento da Página (se ativado nas Opções)" + "message": "Preencher automaticamente ao carregar a página (se configurado nas Opções)" }, "autoFillOnPageLoadUseDefault": { "message": "Usar configuração padrão" }, "autoFillOnPageLoadYes": { - "message": "Auto-preencher ao carregar a página" + "message": "Preencher automaticamente ao carregar a página" }, "autoFillOnPageLoadNo": { - "message": "Não auto-preencher ao carregar a página" + "message": "Não preencher automaticamente ao carregar a página" }, "commandOpenPopup": { "message": "Abrir pop-up do cofre" @@ -1636,10 +1696,10 @@ "message": "Preencher automaticamente o último login utilizado para o site atual" }, "commandAutofillCardDesc": { - "message": "Preenchimento automático do último cartão utilizado para o site atual" + "message": "Preencher automaticamente o último cartão utilizado para o site atual" }, "commandAutofillIdentityDesc": { - "message": "Autopreencher a última identidade usada para o site atual" + "message": "Preencher automaticamente a última identidade usada para o site atual" }, "commandGeneratePasswordDesc": { "message": "Gerar e copiar uma nova senha aleatória para a área de transferência." @@ -2144,6 +2204,9 @@ "useThisPassword": { "message": "Use esta senha" }, + "useThisPassphrase": { + "message": "Use this passphrase" + }, "useThisUsername": { "message": "Use este nome de usuário" }, @@ -2167,15 +2230,6 @@ "vaultTimeoutAction1": { "message": "Ação do tempo" }, - "newCustomizationOptionsCalloutTitle": { - "message": "Novas opções de personalização" - }, - "newCustomizationOptionsCalloutContent": { - "message": "Personalize a experiência do seu cofre com ações de cópia rápida, modo compacto e muito mais!" - }, - "newCustomizationOptionsCalloutLink": { - "message": "Ver todas as configurações de aparência" - }, "lock": { "message": "Bloquear", "description": "Verb form: to make secure or inaccessible by" @@ -2212,16 +2266,16 @@ "message": "Confirmação de Ação de Tempo Limite" }, "autoFillAndSave": { - "message": "Autopreencher e Salvar" + "message": "Preencher automaticamente e salvar" }, "fillAndSave": { "message": "Preencher e salvar" }, "autoFillSuccessAndSavedUri": { - "message": "Item Auto-Preenchido e URI Salvo" + "message": "Item preenchido automaticamente e URI salva" }, "autoFillSuccess": { - "message": "Item Auto-Preenchido" + "message": "Item preenchido automaticamente " }, "insecurePageWarning": { "message": "Aviso: Esta é uma página HTTP não segura, e qualquer informação que você enviar poderá ser interceptada e modificada por outras pessoas. Este login foi originalmente salvo em uma página segura (HTTPS)." @@ -2450,10 +2504,10 @@ "message": "O Bitwarden não irá pedir para salvar os detalhes de credencial para estes domínios. Você deve atualizar a página para que as alterações entrem em vigor." }, "blockedDomainsDesc": { - "message": "\"Autopreencher\" e outros recursos podem não estar disponíveis para estes sites. Atualize a página para que as mudanças surtam efeito." + "message": "O preenchimento automático e outros recursos podem não estar disponíveis para estes sites. Atualize a página para que as mudanças surtam efeito." }, "autofillBlockedNoticeV2": { - "message": "\"Auto completar\" está bloqueado para este site." + "message": "O preenchimento automático está bloqueado para este site." }, "autofillBlockedNoticeGuidance": { "message": "Altere isso em configurações" @@ -2623,6 +2677,10 @@ "message": "Todos os Sends", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, + "maxAccessCountReached": { + "message": "Max access count reached", + "description": "This text will be displayed after a Send has been accessed the maximum amount of times." + }, "hideTextByDefault": { "message": "Ocultar texto por padrão" }, @@ -2963,14 +3021,14 @@ "copyCustomFieldNameNotUnique": { "message": "Nenhum identificador exclusivo encontrado." }, - "convertOrganizationEncryptionDesc": { - "message": "$ORGANIZATION$ está usando SSO com um servidor de chaves auto-hospedado. Não é mais necessária uma senha mestra para os membros desta organização entrarem.", - "placeholders": { - "organization": { - "content": "$1", - "example": "My Org Name" - } - } + "removeMasterPasswordForOrganizationUserKeyConnector": { + "message": "A master password is no longer required for members of the following organization. Please confirm the domain below with your organization administrator." + }, + "organizationName": { + "message": "Organization name" + }, + "keyConnectorDomain": { + "message": "Key Connector domain" }, "leaveOrganization": { "message": "Sair da Organização" @@ -3388,10 +3446,10 @@ } }, "autofillPageLoadPolicyActivated": { - "message": "Suas políticas de organização ativaram o autopreenchimento ao carregar a página." + "message": "Suas políticas de organização ativaram o preenchimento automático ao carregar a página." }, "howToAutofill": { - "message": "Como autopreencher" + "message": "Como preencher automaticamente" }, "autofillSelectInfoWithCommand": { "message": "Selecione um item desta tela, use o atalho $COMMAND$, ou explore outras opções nas configurações.", @@ -3409,10 +3467,10 @@ "message": "Entendi" }, "autofillSettings": { - "message": "Configurações de autopreenchimento" + "message": "Configurações de preenchimento automático" }, "autofillKeyboardShortcutSectionTitle": { - "message": "Autofill shortcut" + "message": "Atalho de preenchimento automático" }, "autofillKeyboardShortcutUpdateLabel": { "message": "Alterar atalho" @@ -3421,13 +3479,13 @@ "message": "Gerenciar atalhos" }, "autofillShortcut": { - "message": "Atalho para autopreenchimento" + "message": "Atalho de teclado para preenchimento automático" }, "autofillLoginShortcutNotSet": { - "message": "O atalho de acesso ao preenchimento automático não está definido. Altere isso nas configurações do navegador." + "message": "O atalho do preenchimento automático não está definido. Altere isso nas configurações do navegador." }, "autofillLoginShortcutText": { - "message": "O atalho de login de preenchimento automático é $COMMAND$. Gerencie todos os atalhos nas configurações do navegador.", + "message": "O atalho de preenchimento automático é $COMMAND$. Gerencie todos os atalhos nas configurações do navegador.", "placeholders": { "command": { "content": "$1", @@ -3436,7 +3494,7 @@ } }, "autofillShortcutTextSafari": { - "message": "Atalho padrão de autopreenchimento: $COMMAND$.", + "message": "Atalho padrão de preenchimento automático: $COMMAND$.", "placeholders": { "command": { "content": "$1", @@ -3532,12 +3590,36 @@ "deviceTrusted": { "message": "Dispositivo confiável" }, - "sendsNoItemsTitle": { - "message": "Nenhum Send ativo", + "trustOrganization": { + "message": "Trust organization" + }, + "trust": { + "message": "Trust" + }, + "doNotTrust": { + "message": "Do not trust" + }, + "organizationNotTrusted": { + "message": "Organization is not trusted" + }, + "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" + }, + "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." + }, + "orgTrustWarning1": { + "message": "This organization has an Enterprise policy that will enroll you in account recovery. Enrollment will allow organization administrators to change your password. Only proceed if you recognize this organization and the fingerprint phrase displayed below matches the organization's fingerprint." + }, + "trustUser": { + "message": "Trust user" + }, + "sendsTitleNoItems": { + "message": "Send sensitive information safely", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, - "sendsNoItemsMessage": { - "message": "Use o Send para compartilhar informação criptografa com qualquer um.", + "sendsBodyNoItems": { + "message": "Share files and data securely with anyone, on any platform. Your information will remain end-to-end encrypted while limiting exposure.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "inputRequired": { @@ -3660,11 +3742,11 @@ "message": "Alias do domínio" }, "passwordRepromptDisabledAutofillOnPageLoad": { - "message": "Os itens com confirmação de senha mestra não podem ser auto-preenchidos ao carregar a página. O carregamento da página será desativado.", + "message": "Os itens com confirmação de senha mestra não podem ser preenchidos automaticamente ao carregar a página. O preenchimento automático ao carregar a página será desativado.", "description": "Toast message for describing that master password re-prompt cannot be autofilled on page load." }, "autofillOnPageLoadSetToDefault": { - "message": "Definir preenchimento automático ao carregar página para usar a configuração padrão.", + "message": "O preenchimento automático ao carregar a página está usando a configuração padrão.", "description": "Toast message for informing the user that autofill on page load has been set to the default setting." }, "turnOffMasterPasswordPromptToEditField": { @@ -3678,15 +3760,15 @@ "message": "Ir para o conteúdo" }, "bitwardenOverlayButton": { - "message": "Botão de Menu de Autopreenchimento Bitwarden", + "message": "Botão de menu de preenchimento automático do Bitwarden", "description": "Page title for the iframe containing the overlay button" }, "toggleBitwardenVaultOverlay": { - "message": "Alternar menu de autopreenchimento do Bitwarden", + "message": "Ativar menu de preenchimento automático do Bitwarden", "description": "Screen reader and tool tip label for the overlay button" }, "bitwardenVault": { - "message": "Menu de autopreenchimento Bitwarden", + "message": "Menu de preenchimento automático do Bitwarden", "description": "Page title in overlay" }, "unlockYourAccountToViewMatchingLogins": { @@ -3706,11 +3788,11 @@ "description": "Screen reader text (aria-label) for unlock account button in overlay" }, "totpCodeAria": { - "message": "Código de Verificação TOTP", + "message": "Código de verificação TOTP", "description": "Aria label for the totp code displayed in the inline menu for autofill" }, "totpSecondsSpanAria": { - "message": "Tempo até expirar o código", + "message": "Tempo até o código expirar", "description": "Aria label for the totp seconds displayed in the inline menu for autofill" }, "fillCredentialsFor": { @@ -3758,7 +3840,7 @@ "description": "Screen reader text (aria-label) for new identity button within inline menu" }, "bitwardenOverlayMenuAvailable": { - "message": "Menu de autopreenchimento do Bitwarden disponível. Pressione a tecla de seta para baixo para selecionar.", + "message": "Menu de preenchimento automático do Bitwarden disponível. Pressione a tecla de seta para baixo para selecionar.", "description": "Screen reader text for announcing when the overlay opens on the page" }, "turnOn": { @@ -4135,7 +4217,7 @@ "description": "Title for dialog which asks if the user wants to proceed to a relevant Help Center page" }, "confirmContinueToHelpCenterPasswordManagementContent": { - "message": "Alterar as configurações de autopreenchimento e gerenciamento de senhas do seu navegador.", + "message": "Altere as configurações de preenchimento automático e gerenciamento de senhas do seu navegador.", "description": "Body content for dialog which asks if the user wants to proceed to the Help Center's page about browser password management settings" }, "confirmContinueToHelpCenterKeyboardShortcutsContent": { @@ -4143,7 +4225,7 @@ "description": "Body content for dialog which asks if the user wants to proceed to the Help Center's page about browser keyboard shortcut settings" }, "confirmContinueToBrowserPasswordManagementSettingsContent": { - "message": "Alterar as configurações de autopreenchimento e gerenciamento de senhas do seu navegador.", + "message": "Altere as configurações de preenchimento automático e gerenciamento de senhas do seu navegador.", "description": "Body content for dialog which asks if the user wants to proceed to the browser's password management settings page" }, "confirmContinueToBrowserKeyboardShortcutSettingsContent": { @@ -4155,7 +4237,7 @@ "description": "Dialog title facilitating the ability to override a chrome browser's default autofill behavior" }, "overrideDefaultBrowserAutofillDescription": { - "message": "Ignorar esta opção pode causar conflitos entre o menu de autopreenchimento do Bitwarden e o do seu navegador.", + "message": "Ignorar esta opção pode causar conflitos entre o menu de preenchimento automático do Bitwarden e o do seu navegador.", "description": "Dialog message facilitating the ability to override a chrome browser's default autofill behavior" }, "overrideDefaultBrowserAutoFillSettings": { @@ -4286,7 +4368,7 @@ } }, "autofillTitle": { - "message": "Auto-preenchimento - $ITEMNAME$", + "message": "Preencher automaticamente - $ITEMNAME$", "description": "Title for a button that autofills a login item.", "placeholders": { "itemname": { @@ -4296,7 +4378,7 @@ } }, "autofillTitleWithField": { - "message": "Autocompletar - $ITEMNAME$ - $FIELD$", + "message": "Preencher automaticamente - $ITEMNAME$ - $FIELD$", "description": "Title for a button that autofills a login item.", "placeholders": { "itemname": { @@ -4456,6 +4538,33 @@ } } }, + "downloadBitwarden": { + "message": "Download Bitwarden" + }, + "downloadBitwardenOnAllDevices": { + "message": "Download Bitwarden on all devices" + }, + "getTheMobileApp": { + "message": "Get the mobile app" + }, + "getTheMobileAppDesc": { + "message": "Access your passwords on the go with the Bitwarden mobile app." + }, + "getTheDesktopApp": { + "message": "Get the desktop app" + }, + "getTheDesktopAppDesc": { + "message": "Access your vault without a browser, then set up unlock with biometrics to expedite unlocking in both the desktop app and browser extension." + }, + "downloadFromBitwardenNow": { + "message": "Download from bitwarden.com now" + }, + "getItOnGooglePlay": { + "message": "Get it on Google Play" + }, + "downloadOnTheAppStore": { + "message": "Download on the App Store" + }, "permanentlyDeleteAttachmentConfirmation": { "message": "Tem certeza de que deseja excluir este anexo permanentemente?" }, @@ -4512,7 +4621,7 @@ "message": "Chave do autenticador" }, "autofillOptions": { - "message": "Opções de autopreenchimento" + "message": "Opções de preenchimento automático" }, "websiteUri": { "message": "Site (URI)" @@ -4565,7 +4674,7 @@ } }, "autoFillOnPageLoad": { - "message": "Preenchimento automático ao carregar a página?" + "message": "Preencher automaticamente ao carregar a página?" }, "cardExpiredTitle": { "message": "Cartão expirado" @@ -4655,7 +4764,7 @@ "message": "Use caixas de seleção se gostaria de preencher automaticamente a caixa de seleção de um formulário, como um e-mail de lembrança" }, "linkedHelpText": { - "message": "Use um campo vinculado quando estiver enfrentando problemas com o auto-preenchimento com um site específico." + "message": "Use um campo vinculado quando estiver enfrentando problemas com o preenchimento automático com um site específico." }, "linkedLabelHelpText": { "message": "Digite o Id html do campo, nome, nome aria-label, ou espaço reservado." @@ -4917,6 +5026,15 @@ "biometricsStatusHelptextUnavailableReasonUnknown": { "message": "O desbloqueio por biometria está indisponível por razões desconhecidas." }, + "unlockVault": { + "message": "Unlock your vault in seconds" + }, + "unlockVaultDesc": { + "message": "You can customize your unlock and timeout settings to more quickly access your vault." + }, + "unlockPinSet": { + "message": "Unlock PIN set" + }, "authenticating": { "message": "Autenticando" }, @@ -4928,8 +5046,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": { @@ -5079,42 +5197,6 @@ "beta": { "message": "Beta" }, - "importantNotice": { - "message": "Aviso importante" - }, - "setupTwoStepLogin": { - "message": "Configurar login em duas etapas" - }, - "newDeviceVerificationNoticeContentPage1": { - "message": "Bitwarden enviará um código para o seu e-mail para verificar novos dispositivos a partir de fevereiro de 2025." - }, - "newDeviceVerificationNoticeContentPage2": { - "message": "Você pode configurar o login em duas etapas como uma forma alternativa de proteger sua conta ou mudar seu e-mail para um que você possa acessar." - }, - "remindMeLater": { - "message": "Lembre-me depois" - }, - "newDeviceVerificationNoticePageOneFormContent": { - "message": "Você tem acesso ao seu e-mail, $EMAIL$?", - "placeholders": { - "email": { - "content": "$1", - "example": "your_name@email.com" - } - } - }, - "newDeviceVerificationNoticePageOneEmailAccessNo": { - "message": "Não tenho" - }, - "newDeviceVerificationNoticePageOneEmailAccessYes": { - "message": "Sim, posso acessar meu e-mail de forma confiável" - }, - "turnOnTwoStepLogin": { - "message": "Ativar login em duas etapas" - }, - "changeAcctEmail": { - "message": "Alterar e-mail" - }, "extensionWidth": { "message": "Largura da janela" }, @@ -5169,6 +5251,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" }, @@ -5182,7 +5270,7 @@ "message": "Login rápido e fácil" }, "quickLoginBody": { - "message": "Ative o desbloqueio por biometria e o autopreenchimento para acessar suas contas sem digitar uma única letra." + "message": "Ative o desbloqueio por biometria e o preenchimento automático para acessar suas contas sem digitar uma única letra." }, "secureUser": { "message": "Melhore seus logins de nível" @@ -5195,5 +5283,98 @@ }, "secureDevicesBody": { "message": "Guarde quantas senhas quiser e acesse de qualquer lugar com o Bitwarden. No seu celular, navegador e computador." + }, + "nudgeBadgeAria": { + "message": "1 notification" + }, + "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!" + }, + "hasItemsVaultNudgeBodyOne": { + "message": "Autofill items for the current page" + }, + "hasItemsVaultNudgeBodyTwo": { + "message": "Favorite items for easy access" + }, + "hasItemsVaultNudgeBodyThree": { + "message": "Search your vault for something else" + }, + "newLoginNudgeTitle": { + "message": "Seja mais rápido com o preenchimento automático" + }, + "newLoginNudgeBodyOne": { + "message": "Include a", + "description": "This is in multiple parts to allow for bold text in the middle of the sentence.", + "example": "Include a Website so this login appears as an autofill suggestion." + }, + "newLoginNudgeBodyBold": { + "message": "Website", + "description": "This is in multiple parts to allow for bold text in the middle of the sentence.", + "example": "Include a Website so this login appears as an autofill suggestion." + }, + "newLoginNudgeBodyTwo": { + "message": "so this login appears as an autofill suggestion.", + "description": "This is in multiple parts to allow for bold text in the middle of the sentence.", + "example": "Include a Website so this login appears as an autofill suggestion." + }, + "newCardNudgeTitle": { + "message": "Seamless online checkout" + }, + "newCardNudgeBody": { + "message": "Preencha automaticamente formulários de pagamento com cartões de forma segura e precisa." + }, + "newIdentityNudgeTitle": { + "message": "Simplify creating accounts" + }, + "newIdentityNudgeBody": { + "message": "Preencha automaticamente formulários longos de registro ou contato de forma rápida." + }, + "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" + }, + "newSshNudgeBodyOne": { + "message": "Store your keys and connect with the SSH agent for fast, encrypted authentication.", + "description": "Two part message", + "example": "Store your keys and connect with the SSH agent for fast, encrypted authentication. Learn more about SSH agent" + }, + "newSshNudgeBodyTwo": { + "message": "Learn more about SSH agent", + "description": "Two part message", + "example": "Store your keys and connect with the SSH agent for fast, encrypted authentication. Learn more about SSH agent" + }, + "generatorNudgeTitle": { + "message": "Quickly create passwords" + }, + "generatorNudgeBodyOne": { + "message": "Easily create strong and unique passwords by clicking on", + "description": "Two part message", + "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." + }, + "generatorNudgeBodyTwo": { + "message": "to help you keep your logins secure.", + "description": "Two part message", + "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." + }, + "generatorNudgeBodyAria": { + "message": "Easily create strong and unique passwords by clicking on the Generate password button to help you keep your logins secure.", + "description": "Aria label for the body content of the generator nudge" + }, + "noPermissionsViewPage": { + "message": "You do not have permissions to view this page. Try logging in with a different account." } } diff --git a/apps/browser/src/_locales/pt_PT/messages.json b/apps/browser/src/_locales/pt_PT/messages.json index daab7fb05d9..c88f9b9b5eb 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,55 @@ "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.", + "notificationNewItemAria": { + "message": "Novo item, abre numa nova janela", + "description": "Aria label for the new item button in notification bar confirmation message when error is prompted" + }, + "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": { - "content": "$1" + "label": { + "content": "$1", + "example": "Login" } - }, - "description": "Shown to user after login is updated." + } + }, + "notificationLoginSaveConfirmation": { + "message": "guardado no Bitwarden.", + "description": "Shown to user after item is saved." + }, + "notificationLoginUpdatedConfirmation": { + "message": "atualizado no Bitwarden.", + "description": "Shown to user after item is updated." + }, + "selectItemAriaLabel": { + "message": "Selecionar $ITEMTYPE$, $ITEMNAME$", + "description": "Used by screen readers. $1 is the item type (like vault or folder), $2 is the selected item name.", + "placeholders": { + "itemType": { + "content": "$1" + }, + "itemName": { + "content": "$2" + } + } }, "saveAsNewLoginAction": { "message": "Guardar como nova credencial", @@ -1082,12 +1120,16 @@ "message": "Atualizar credencial", "description": "Button text for updating an existing login entry." }, - "saveLoginPrompt": { - "message": "Guardar credencial?", + "unlockToSave": { + "message": "Desbloqueie para guardar esta credencial", + "description": "User prompt to take action in order to save the login they just entered." + }, + "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": { @@ -1323,8 +1365,8 @@ "featureUnavailable": { "message": "Funcionalidade indisponível" }, - "encryptionKeyMigrationRequired": { - "message": "É necessária a migração da chave de encriptação. Inicie sessão através do cofre Web para atualizar a sua chave de encriptação." + "legacyEncryptionUnsupported": { + "message": "A encriptação herdada já não é suportada. Por favor, contacte o suporte para recuperar a sua conta." }, "premiumMembership": { "message": "Subscrição Premium" @@ -1557,6 +1599,24 @@ "autofillSuggestionsSectionTitle": { "message": "Sugestões de preenchimento automático" }, + "autofillSpotlightTitle": { + "message": "Encontre facilmente sugestões de preenchimento automático" + }, + "autofillSpotlightDesc": { + "message": "Desative as definições de preenchimento automático do seu navegador, para que não entrem em conflito com o Bitwarden." + }, + "turnOffBrowserAutofill": { + "message": "Desative o preenchimento automático do $BROWSER$", + "placeholders": { + "browser": { + "content": "$1", + "example": "Chrome" + } + } + }, + "turnOffAutofill": { + "message": "Desativar o preenchimento automático" + }, "showInlineMenuLabel": { "message": "Mostrar sugestões de preenchimento automático nos campos do formulário" }, @@ -2144,6 +2204,9 @@ "useThisPassword": { "message": "Utilizar esta palavra-passe" }, + "useThisPassphrase": { + "message": "Utilizar esta frase de acesso" + }, "useThisUsername": { "message": "Utilizar este nome de utilizador" }, @@ -2167,15 +2230,6 @@ "vaultTimeoutAction1": { "message": "Ação de tempo limite" }, - "newCustomizationOptionsCalloutTitle": { - "message": "Novas opções de personalização" - }, - "newCustomizationOptionsCalloutContent": { - "message": "Personalize a sua experiência no cofre com ações de cópia rápida, modo compacto e muito mais!" - }, - "newCustomizationOptionsCalloutLink": { - "message": "Ver todas as definições de Aspeto" - }, "lock": { "message": "Bloquear", "description": "Verb form: to make secure or inaccessible by" @@ -2623,6 +2677,10 @@ "message": "Todos os Sends", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, + "maxAccessCountReached": { + "message": "Número máximo de acessos atingido", + "description": "This text will be displayed after a Send has been accessed the maximum amount of times." + }, "hideTextByDefault": { "message": "Ocultar texto por predefinição" }, @@ -2963,14 +3021,14 @@ "copyCustomFieldNameNotUnique": { "message": "Não foi encontrado um identificador único." }, - "convertOrganizationEncryptionDesc": { - "message": "A $ORGANIZATION$ está a utilizar o SSO com um servidor de chaves auto-hospedado. Já não é necessária uma palavra-passe mestra para iniciar sessão para os membros desta organização.", - "placeholders": { - "organization": { - "content": "$1", - "example": "My Org Name" - } - } + "removeMasterPasswordForOrganizationUserKeyConnector": { + "message": "Já não é necessária uma palavra-passe mestra para os membros da seguinte organização. Por favor, confirme o domínio abaixo com o administrador da sua organização." + }, + "organizationName": { + "message": "Nome da organização" + }, + "keyConnectorDomain": { + "message": "Domínio do Key Connector" }, "leaveOrganization": { "message": "Sair da organização" @@ -3532,12 +3590,36 @@ "deviceTrusted": { "message": "Dispositivo de confiança" }, - "sendsNoItemsTitle": { - "message": "Sem Sends ativos", + "trustOrganization": { + "message": "Confiar na organização" + }, + "trust": { + "message": "Confiar" + }, + "doNotTrust": { + "message": "Não confiar" + }, + "organizationNotTrusted": { + "message": "A organização não é de confiança" + }, + "emergencyAccessTrustWarning": { + "message": "Para segurança da sua conta, confirme apenas se tiver concedido acesso de emergência a este utilizador e se a sua impressão digital corresponder à que é apresentada na sua conta" + }, + "orgTrustWarning": { + "message": "Para segurança da sua conta, prossiga apenas se for membro desta organização, tiver a recuperação de conta ativada e a impressão digital apresentada abaixo corresponder à impressão digital da organização." + }, + "orgTrustWarning1": { + "message": "Esta organização tem uma política empresarial que o registará na recuperação de conta. A inscrição permitirá que os administradores da organização alterem a sua palavra-passe. Avance apenas se reconhecer esta organização e se a frase de impressão digital apresentada abaixo corresponder à impressão digital da organização." + }, + "trustUser": { + "message": "Confiar no utilizador" + }, + "sendsTitleNoItems": { + "message": "Envie informações sensíveis com segurança", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, - "sendsNoItemsMessage": { - "message": "Utilize o Send para partilhar de forma segura informações encriptadas com qualquer pessoa.", + "sendsBodyNoItems": { + "message": "Partilhe ficheiros e dados de forma segura com qualquer pessoa, em qualquer plataforma. As suas informações permanecerão encriptadas ponto a ponto, limitando a exposição.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "inputRequired": { @@ -4456,6 +4538,33 @@ } } }, + "downloadBitwarden": { + "message": "Descarregar o Bitwarden" + }, + "downloadBitwardenOnAllDevices": { + "message": "Descarregue o Bitwarden em todos os dispositivos" + }, + "getTheMobileApp": { + "message": "Obtenha a app móvel" + }, + "getTheMobileAppDesc": { + "message": "Aceda às suas palavras-passe em qualquer lugar com a app móvel Bitwarden." + }, + "getTheDesktopApp": { + "message": "Obtenha a app para computador" + }, + "getTheDesktopAppDesc": { + "message": "Aceda ao seu cofre sem um navegador e, em seguida, configure o desbloqueio com biometria para acelerar o desbloqueio na app para computador e na extensão do navegador." + }, + "downloadFromBitwardenNow": { + "message": "Descarregue já a partir de bitwarden.com" + }, + "getItOnGooglePlay": { + "message": "Obtenha-a na Google Play" + }, + "downloadOnTheAppStore": { + "message": "Descarregue na App Store" + }, "permanentlyDeleteAttachmentConfirmation": { "message": "Tem a certeza de que pretende eliminar permanentemente este anexo?" }, @@ -4917,6 +5026,15 @@ "biometricsStatusHelptextUnavailableReasonUnknown": { "message": "O desbloqueio biométrico está atualmente indisponível por um motivo desconhecido." }, + "unlockVault": { + "message": "Desbloqueie o seu cofre em segundos" + }, + "unlockVaultDesc": { + "message": "Pode personalizar as suas definições de desbloqueio e de tempo limite para aceder mais rapidamente ao seu cofre." + }, + "unlockPinSet": { + "message": "Definição do PIN de desbloqueio" + }, "authenticating": { "message": "A autenticar" }, @@ -4928,8 +5046,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": { @@ -5079,42 +5197,6 @@ "beta": { "message": "Beta" }, - "importantNotice": { - "message": "Aviso importante" - }, - "setupTwoStepLogin": { - "message": "Definir a verificação de dois passos" - }, - "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." - }, - "newDeviceVerificationNoticeContentPage2": { - "message": "Pode configurar a verificação de dois passos como forma alternativa de proteger a sua conta ou alterar o seu e-mail para um a que possa aceder." - }, - "remindMeLater": { - "message": "Lembrar-me mais tarde" - }, - "newDeviceVerificationNoticePageOneFormContent": { - "message": "Tem um acesso fiável ao seu e-mail, $EMAIL$?", - "placeholders": { - "email": { - "content": "$1", - "example": "your_name@email.com" - } - } - }, - "newDeviceVerificationNoticePageOneEmailAccessNo": { - "message": "Não, não tenho" - }, - "newDeviceVerificationNoticePageOneEmailAccessYes": { - "message": "Sim, consigo aceder de forma fiável ao meu e-mail" - }, - "turnOnTwoStepLogin": { - "message": "Ativar a verificação de dois passos" - }, - "changeAcctEmail": { - "message": "Alterar o e-mail da conta" - }, "extensionWidth": { "message": "Largura da extensão" }, @@ -5169,6 +5251,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 +5283,98 @@ }, "secureDevicesBody": { "message": "Guarde palavras-passe ilimitadas em dispositivos ilimitados com as apps Bitwarden para telemóvel, navegador e computador." + }, + "nudgeBadgeAria": { + "message": "1 notificação" + }, + "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!" + }, + "hasItemsVaultNudgeBodyOne": { + "message": "Preenchimento automático de itens para a página atual" + }, + "hasItemsVaultNudgeBodyTwo": { + "message": "Adicione itens aos favoritos para fácil acesso" + }, + "hasItemsVaultNudgeBodyThree": { + "message": "Procure outras coisas no seu cofre" + }, + "newLoginNudgeTitle": { + "message": "Poupe tempo com o preenchimento automático" + }, + "newLoginNudgeBodyOne": { + "message": "Inclua um", + "description": "This is in multiple parts to allow for bold text in the middle of the sentence.", + "example": "Include a Website so this login appears as an autofill suggestion." + }, + "newLoginNudgeBodyBold": { + "message": "site", + "description": "This is in multiple parts to allow for bold text in the middle of the sentence.", + "example": "Include a Website so this login appears as an autofill suggestion." + }, + "newLoginNudgeBodyTwo": { + "message": "para que esta credencial apareça como uma sugestão de preenchimento automático.", + "description": "This is in multiple parts to allow for bold text in the middle of the sentence.", + "example": "Include a Website so this login appears as an autofill suggestion." + }, + "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" + }, + "newSshNudgeBodyOne": { + "message": "Guarde as suas chaves e ligue-se ao agente SSH para uma autenticação rápida e encriptada.", + "description": "Two part message", + "example": "Store your keys and connect with the SSH agent for fast, encrypted authentication. Learn more about SSH agent" + }, + "newSshNudgeBodyTwo": { + "message": "Saiba mais sobre o agente SSH", + "description": "Two part message", + "example": "Store your keys and connect with the SSH agent for fast, encrypted authentication. Learn more about SSH agent" + }, + "generatorNudgeTitle": { + "message": "Criar rapidamente palavras-passe" + }, + "generatorNudgeBodyOne": { + "message": "Crie facilmente palavras-passe fortes e únicas clicando em", + "description": "Two part message", + "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." + }, + "generatorNudgeBodyTwo": { + "message": "para o ajudar a manter as suas credenciais seguras.", + "description": "Two part message", + "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." + }, + "generatorNudgeBodyAria": { + "message": "Crie facilmente palavras-passe fortes e únicas clicando no botão Gerar palavra-passe para o ajudar a manter as suas credenciais seguras.", + "description": "Aria label for the body content of the generator nudge" + }, + "noPermissionsViewPage": { + "message": "Não tem permissões para ver esta página. Tente iniciar sessão com uma conta diferente." } } diff --git a/apps/browser/src/_locales/ro/messages.json b/apps/browser/src/_locales/ro/messages.json index bcdf10f37f1..4342afc635f 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)" @@ -20,16 +23,16 @@ "message": "Creare cont" }, "newToBitwarden": { - "message": "New to Bitwarden?" + "message": "Nou pe Bitwarden?" }, "logInWithPasskey": { - "message": "Log in with passkey" + "message": "Autentificare cu parolă" }, "useSingleSignOn": { - "message": "Use single sign-on" + "message": "Autentificare unică" }, "welcomeBack": { - "message": "Welcome back" + "message": "Bine ați revenit" }, "setAStrongPassword": { "message": "Setați o parolă puternică" @@ -81,7 +84,7 @@ "message": "Indiciu pentru parola principală (opțional)" }, "passwordStrengthScore": { - "message": "Password strength score $SCORE$", + "message": "Nivelul de siguranța al parolei $SCORE$", "placeholders": { "score": { "content": "$1", @@ -129,7 +132,7 @@ "message": "Copiere parolă" }, "copyPassphrase": { - "message": "Copy passphrase" + "message": "Copiază întrebarea de siguranța" }, "copyNote": { "message": "Copiere notă" @@ -162,13 +165,13 @@ "message": "Copiați numărul de licență" }, "copyPrivateKey": { - "message": "Copy private key" + "message": "Copiază cheia de siguranța privată" }, "copyPublicKey": { - "message": "Copy public key" + "message": "Copiază cheia de siguranța publică" }, "copyFingerprint": { - "message": "Copy fingerprint" + "message": "Copiați amprenta" }, "copyCustomField": { "message": "Copiază $FIELD$", @@ -186,11 +189,11 @@ "message": "Copiază notițele" }, "copy": { - "message": "Copy", + "message": "Copiați", "description": "Copy to clipboard" }, "fill": { - "message": "Fill", + "message": "Completați", "description": "This string is used on the vault page to indicate autofilling. Horizontal space is limited in the interface here so try and keep translations as concise as possible." }, "autoFill": { @@ -206,10 +209,10 @@ "message": "Autocompletare identitate" }, "fillVerificationCode": { - "message": "Fill verification code" + "message": "Completați codul de verificare" }, "fillVerificationCodeAria": { - "message": "Fill Verification Code", + "message": "Completați Codul de Verificare", "description": "Aria label for the heading displayed the inline menu for totp code autofill" }, "generatePasswordCopied": { @@ -404,7 +407,7 @@ "message": "Create folders to organize your vault items" }, "deleteFolderPermanently": { - "message": "Are you sure you want to permanently delete this folder?" + "message": "Ești sigur că dorești să ștergi permanent acest fișier?" }, "deleteFolder": { "message": "Ștergere dosar" @@ -459,19 +462,19 @@ "message": "Generare parolă" }, "generatePassphrase": { - "message": "Generate passphrase" + "message": "Creează întrebarea de siguranța" }, "passwordGenerated": { - "message": "Password generated" + "message": "Parola creată" }, "passphraseGenerated": { - "message": "Passphrase generated" + "message": "Întrebare de siguranța creată" }, "usernameGenerated": { - "message": "Username generated" + "message": "Nume de utilizator creat" }, "emailGenerated": { - "message": "Email generated" + "message": "E-mail creat" }, "regeneratePassword": { "message": "Regenerare parolă" @@ -487,7 +490,7 @@ "description": "Card header for password generator include block" }, "uppercaseDescription": { - "message": "Include uppercase characters", + "message": "Includeți caractere mari", "description": "Tooltip for the password generator uppercase character checkbox" }, "uppercaseLabel": { @@ -495,7 +498,7 @@ "description": "Label for the password generator uppercase character checkbox" }, "lowercaseDescription": { - "message": "Include lowercase characters", + "message": "Includeți caractere mici", "description": "Full description for the password generator lowercase character checkbox" }, "lowercaseLabel": { @@ -503,7 +506,7 @@ "description": "Label for the password generator lowercase character checkbox" }, "numbersDescription": { - "message": "Include numbers", + "message": "Includeți cifre", "description": "Full description for the password generator numbers checkbox" }, "numbersLabel": { @@ -511,7 +514,7 @@ "description": "Label for the password generator numbers checkbox" }, "specialCharactersDescription": { - "message": "Include special characters", + "message": "Includeți caractere speciale", "description": "Full description for the password generator special characters checkbox" }, "numWords": { @@ -534,7 +537,7 @@ "message": "Minim de caractere speciale" }, "avoidAmbiguous": { - "message": "Avoid ambiguous characters", + "message": "Evită caracterele ambigue", "description": "Label for the avoid ambiguous characters checkbox." }, "generatorPolicyInEffect": { @@ -584,7 +587,7 @@ "message": "Note" }, "privateNote": { - "message": "Private note" + "message": "Notă privată" }, "note": { "message": "Notă" @@ -653,7 +656,7 @@ "message": "Browserul dvs. nu acceptă copierea în clipboard. Transcrieți datele manual." }, "verifyYourIdentity": { - "message": "Verify your identity" + "message": "Verificați- vă identitatea" }, "weDontRecognizeThisDevice": { "message": "We don't recognize this device. Enter the code sent to your email to verify your identity." @@ -668,10 +671,10 @@ "message": "Your vault is locked" }, "yourAccountIsLocked": { - "message": "Your account is locked" + "message": "Contul dumneavoastră este blocat" }, "or": { - "message": "or" + "message": "sau" }, "unlock": { "message": "Deblocare" @@ -848,7 +851,7 @@ "message": "Bitwarden poate stoca și completa coduri de verificare în doi pași. Selectați pictograma camerei foto pentru a face o captură de ecran a codului QR de autentificare al acestui site, sau copiați și lipiți cheia în acest câmp." }, "learnMoreAboutAuthenticators": { - "message": "Learn more about authenticators" + "message": "Află mai multe despre autentificatori" }, "copyTOTP": { "message": "Copiați cheia de autentificare (TOTP)" @@ -872,7 +875,7 @@ "message": "Enter the code sent to your email" }, "enterTheCodeFromYourAuthenticatorApp": { - "message": "Enter the code from your authenticator app" + "message": "Introdu codul din aplicația de autentificare" }, "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": "Reporniți înregistrarea" }, @@ -1056,23 +1062,55 @@ "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.", + "notificationNewItemAria": { + "message": "New Item, opens in new window", + "description": "Aria label for the new item button in notification bar confirmation message when error is prompted" + }, + "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": { - "content": "$1" + "label": { + "content": "$1", + "example": "Login" } - }, - "description": "Shown to user after login is updated." + } + }, + "notificationLoginSaveConfirmation": { + "message": "saved to Bitwarden.", + "description": "Shown to user after item is saved." + }, + "notificationLoginUpdatedConfirmation": { + "message": "updated in Bitwarden.", + "description": "Shown to user after item is updated." + }, + "selectItemAriaLabel": { + "message": "Select $ITEMTYPE$, $ITEMNAME$", + "description": "Used by screen readers. $1 is the item type (like vault or folder), $2 is the selected item name.", + "placeholders": { + "itemType": { + "content": "$1" + }, + "itemName": { + "content": "$2" + } + } }, "saveAsNewLoginAction": { "message": "Save as new login", @@ -1082,12 +1120,16 @@ "message": "Update login", "description": "Button text for updating an existing login entry." }, - "saveLoginPrompt": { - "message": "Save login?", + "unlockToSave": { + "message": "Unlock to save this login", + "description": "User prompt to take action in order to save the login they just entered." + }, + "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": { @@ -1323,8 +1365,8 @@ "featureUnavailable": { "message": "Funcție indisponibilă" }, - "encryptionKeyMigrationRequired": { - "message": "Este necesară migrarea cheilor de criptare. Autentificați-vă prin intermediul seifului web pentru a vă actualiza cheia de criptare." + "legacyEncryptionUnsupported": { + "message": "Legacy encryption is no longer supported. Please contact support to recover your account." }, "premiumMembership": { "message": "Abonament Premium" @@ -1557,6 +1599,24 @@ "autofillSuggestionsSectionTitle": { "message": "Autofill suggestions" }, + "autofillSpotlightTitle": { + "message": "Easily find autofill suggestions" + }, + "autofillSpotlightDesc": { + "message": "Turn off your browser's autofill settings, so they don't conflict with Bitwarden." + }, + "turnOffBrowserAutofill": { + "message": "Turn off $BROWSER$ autofill", + "placeholders": { + "browser": { + "content": "$1", + "example": "Chrome" + } + } + }, + "turnOffAutofill": { + "message": "Turn off autofill" + }, "showInlineMenuLabel": { "message": "Show autofill suggestions on form fields" }, @@ -2144,6 +2204,9 @@ "useThisPassword": { "message": "Use this password" }, + "useThisPassphrase": { + "message": "Use this passphrase" + }, "useThisUsername": { "message": "Use this username" }, @@ -2167,15 +2230,6 @@ "vaultTimeoutAction1": { "message": "Timeout action" }, - "newCustomizationOptionsCalloutTitle": { - "message": "New customization options" - }, - "newCustomizationOptionsCalloutContent": { - "message": "Customize your vault experience with quick copy actions, compact mode, and more!" - }, - "newCustomizationOptionsCalloutLink": { - "message": "View all Appearance settings" - }, "lock": { "message": "Blocare", "description": "Verb form: to make secure or inaccessible by" @@ -2623,6 +2677,10 @@ "message": "Toate Send-urile", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, + "maxAccessCountReached": { + "message": "Max access count reached", + "description": "This text will be displayed after a Send has been accessed the maximum amount of times." + }, "hideTextByDefault": { "message": "Hide text by default" }, @@ -2963,14 +3021,14 @@ "copyCustomFieldNameNotUnique": { "message": "Nu a fost găsit niciun identificator unic." }, - "convertOrganizationEncryptionDesc": { - "message": "$ORGANIZATION$ folosește SSO cu un server de chei autogăzduit. Membrii acestei organizații nu mai au nevoie de o parolă principală pentru autentificare.", - "placeholders": { - "organization": { - "content": "$1", - "example": "My Org Name" - } - } + "removeMasterPasswordForOrganizationUserKeyConnector": { + "message": "A master password is no longer required for members of the following organization. Please confirm the domain below with your organization administrator." + }, + "organizationName": { + "message": "Organization name" + }, + "keyConnectorDomain": { + "message": "Key Connector domain" }, "leaveOrganization": { "message": "Părăsire organizație" @@ -3532,12 +3590,36 @@ "deviceTrusted": { "message": "Dispozitiv de încredere" }, - "sendsNoItemsTitle": { - "message": "No active Sends", + "trustOrganization": { + "message": "Trust organization" + }, + "trust": { + "message": "Trust" + }, + "doNotTrust": { + "message": "Do not trust" + }, + "organizationNotTrusted": { + "message": "Organization is not trusted" + }, + "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" + }, + "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." + }, + "orgTrustWarning1": { + "message": "This organization has an Enterprise policy that will enroll you in account recovery. Enrollment will allow organization administrators to change your password. Only proceed if you recognize this organization and the fingerprint phrase displayed below matches the organization's fingerprint." + }, + "trustUser": { + "message": "Trust user" + }, + "sendsTitleNoItems": { + "message": "Send sensitive information safely", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, - "sendsNoItemsMessage": { - "message": "Use Send to securely share encrypted information with anyone.", + "sendsBodyNoItems": { + "message": "Share files and data securely with anyone, on any platform. Your information will remain end-to-end encrypted while limiting exposure.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "inputRequired": { @@ -4456,6 +4538,33 @@ } } }, + "downloadBitwarden": { + "message": "Download Bitwarden" + }, + "downloadBitwardenOnAllDevices": { + "message": "Download Bitwarden on all devices" + }, + "getTheMobileApp": { + "message": "Get the mobile app" + }, + "getTheMobileAppDesc": { + "message": "Access your passwords on the go with the Bitwarden mobile app." + }, + "getTheDesktopApp": { + "message": "Get the desktop app" + }, + "getTheDesktopAppDesc": { + "message": "Access your vault without a browser, then set up unlock with biometrics to expedite unlocking in both the desktop app and browser extension." + }, + "downloadFromBitwardenNow": { + "message": "Download from bitwarden.com now" + }, + "getItOnGooglePlay": { + "message": "Get it on Google Play" + }, + "downloadOnTheAppStore": { + "message": "Download on the App Store" + }, "permanentlyDeleteAttachmentConfirmation": { "message": "Are you sure you want to permanently delete this attachment?" }, @@ -4917,6 +5026,15 @@ "biometricsStatusHelptextUnavailableReasonUnknown": { "message": "Biometric unlock is currently unavailable for an unknown reason." }, + "unlockVault": { + "message": "Unlock your vault in seconds" + }, + "unlockVaultDesc": { + "message": "You can customize your unlock and timeout settings to more quickly access your vault." + }, + "unlockPinSet": { + "message": "Unlock PIN set" + }, "authenticating": { "message": "Authenticating" }, @@ -4928,8 +5046,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": { @@ -5079,42 +5197,6 @@ "beta": { "message": "Beta" }, - "importantNotice": { - "message": "Important notice" - }, - "setupTwoStepLogin": { - "message": "Set up two-step login" - }, - "newDeviceVerificationNoticeContentPage1": { - "message": "Bitwarden will send a code to your account email to verify logins from new devices starting in February 2025." - }, - "newDeviceVerificationNoticeContentPage2": { - "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" - }, - "newDeviceVerificationNoticePageOneFormContent": { - "message": "Do you have reliable access to your email, $EMAIL$?", - "placeholders": { - "email": { - "content": "$1", - "example": "your_name@email.com" - } - } - }, - "newDeviceVerificationNoticePageOneEmailAccessNo": { - "message": "No, I do not" - }, - "newDeviceVerificationNoticePageOneEmailAccessYes": { - "message": "Yes, I can reliably access my email" - }, - "turnOnTwoStepLogin": { - "message": "Turn on two-step login" - }, - "changeAcctEmail": { - "message": "Change account email" - }, "extensionWidth": { "message": "Extension width" }, @@ -5169,6 +5251,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 +5283,98 @@ }, "secureDevicesBody": { "message": "Save unlimited passwords across unlimited devices with Bitwarden mobile, browser, and desktop apps." + }, + "nudgeBadgeAria": { + "message": "1 notification" + }, + "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!" + }, + "hasItemsVaultNudgeBodyOne": { + "message": "Autofill items for the current page" + }, + "hasItemsVaultNudgeBodyTwo": { + "message": "Favorite items for easy access" + }, + "hasItemsVaultNudgeBodyThree": { + "message": "Search your vault for something else" + }, + "newLoginNudgeTitle": { + "message": "Save time with autofill" + }, + "newLoginNudgeBodyOne": { + "message": "Include a", + "description": "This is in multiple parts to allow for bold text in the middle of the sentence.", + "example": "Include a Website so this login appears as an autofill suggestion." + }, + "newLoginNudgeBodyBold": { + "message": "Website", + "description": "This is in multiple parts to allow for bold text in the middle of the sentence.", + "example": "Include a Website so this login appears as an autofill suggestion." + }, + "newLoginNudgeBodyTwo": { + "message": "so this login appears as an autofill suggestion.", + "description": "This is in multiple parts to allow for bold text in the middle of the sentence.", + "example": "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" + }, + "newSshNudgeBodyOne": { + "message": "Store your keys and connect with the SSH agent for fast, encrypted authentication.", + "description": "Two part message", + "example": "Store your keys and connect with the SSH agent for fast, encrypted authentication. Learn more about SSH agent" + }, + "newSshNudgeBodyTwo": { + "message": "Learn more about SSH agent", + "description": "Two part message", + "example": "Store your keys and connect with the SSH agent for fast, encrypted authentication. Learn more about SSH agent" + }, + "generatorNudgeTitle": { + "message": "Quickly create passwords" + }, + "generatorNudgeBodyOne": { + "message": "Easily create strong and unique passwords by clicking on", + "description": "Two part message", + "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." + }, + "generatorNudgeBodyTwo": { + "message": "to help you keep your logins secure.", + "description": "Two part message", + "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." + }, + "generatorNudgeBodyAria": { + "message": "Easily create strong and unique passwords by clicking on the Generate password button to help you keep your logins secure.", + "description": "Aria label for the body content of the generator nudge" + }, + "noPermissionsViewPage": { + "message": "You do not have permissions to view this page. Try logging in with a different account." } } diff --git a/apps/browser/src/_locales/ru/messages.json b/apps/browser/src/_locales/ru/messages.json index b0f03d34f69..937ad7700c7 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)" @@ -203,7 +206,7 @@ "message": "Автозаполнение карты" }, "autoFillIdentity": { - "message": "Автозаполнение личности" + "message": "Автозаполнение личной информации" }, "fillVerificationCode": { "message": "Заполнить код подтверждения" @@ -225,7 +228,7 @@ "message": "Нет карт" }, "noIdentities": { - "message": "Нет личностей" + "message": "Нет личной информации" }, "addLoginMenu": { "message": "Добавить логин" @@ -234,7 +237,7 @@ "message": "Добавить карту" }, "addIdentityMenu": { - "message": "Добавить личность" + "message": "Добавить личную информацию" }, "unlockVaultMenu": { "message": "Разблокировать хранилище" @@ -883,6 +886,9 @@ "followTheStepsBelowToFinishLoggingIn": { "message": "Следуйте указаниям ниже, чтобы завершить авторизацию." }, + "followTheStepsBelowToFinishLoggingInWithSecurityKey": { + "message": "Выполните следующие шаги, чтобы завершить авторизацию с помощью ключа безопасности." + }, "restartRegistration": { "message": "Перезапустить регистрацию" }, @@ -1031,10 +1037,10 @@ "message": "Всегда показывать личности как предложения автозаполнения при просмотре хранилища" }, "showIdentitiesCurrentTab": { - "message": "Показывать Личности на вкладке" + "message": "Показывать Личную информацию на вкладке" }, "showIdentitiesCurrentTabDesc": { - "message": "Личности будут отображены на вкладке для удобного автозаполнения." + "message": "Личная информация будет отображена на вкладке для удобного автозаполнения." }, "clickToAutofillOnVault": { "message": "Кликните элементы для автозаполнения в режиме просмотра хранилища" @@ -1056,23 +1062,55 @@ "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.", + "notificationNewItemAria": { + "message": "Новый элемент, откроется в новом окне", + "description": "Aria label for the new item button in notification bar confirmation message when error is prompted" + }, + "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": { - "content": "$1" + "label": { + "content": "$1", + "example": "Login" } - }, - "description": "Shown to user after login is updated." + } + }, + "notificationLoginSaveConfirmation": { + "message": "сохранено в Bitwarden.", + "description": "Shown to user after item is saved." + }, + "notificationLoginUpdatedConfirmation": { + "message": "обновлено в Bitwarden.", + "description": "Shown to user after item is updated." + }, + "selectItemAriaLabel": { + "message": "Выберите $ITEMTYPE$, $ITEMNAME$", + "description": "Used by screen readers. $1 is the item type (like vault or folder), $2 is the selected item name.", + "placeholders": { + "itemType": { + "content": "$1" + }, + "itemName": { + "content": "$2" + } + } }, "saveAsNewLoginAction": { "message": "Сохранить как новый логин", @@ -1082,12 +1120,16 @@ "message": "Обновить логин", "description": "Button text for updating an existing login entry." }, - "saveLoginPrompt": { - "message": "Сохранить логин?", + "unlockToSave": { + "message": "Разблокировать, чтобы сохранить этот логин", + "description": "User prompt to take action in order to save the login they just entered." + }, + "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": { @@ -1323,8 +1365,8 @@ "featureUnavailable": { "message": "Функция недоступна" }, - "encryptionKeyMigrationRequired": { - "message": "Требуется миграция ключа шифрования. Чтобы обновить ключ шифрования, войдите через веб-хранилище." + "legacyEncryptionUnsupported": { + "message": "Устаревшее шифрование больше не поддерживается. Для восстановления аккаунта обратитесь в службу поддержки." }, "premiumMembership": { "message": "Премиум" @@ -1557,11 +1599,29 @@ "autofillSuggestionsSectionTitle": { "message": "Предложения по автозаполнению" }, + "autofillSpotlightTitle": { + "message": "Легкий поиск предложений автозаполнения" + }, + "autofillSpotlightDesc": { + "message": "Отключите настройки автозаполнения в браузере, чтобы они не конфликтовали с Bitwarden." + }, + "turnOffBrowserAutofill": { + "message": "Отключить автозаполнение $BROWSER$", + "placeholders": { + "browser": { + "content": "$1", + "example": "Chrome" + } + } + }, + "turnOffAutofill": { + "message": "Отключить автозаполнение" + }, "showInlineMenuLabel": { "message": "Показывать предположения автозаполнения в полях формы" }, "showInlineMenuIdentitiesLabel": { - "message": "Показывать Личности как предложения" + "message": "Показывать Личную информацию как предложения" }, "showInlineMenuCardsLabel": { "message": "Показывать Карты как предложения" @@ -1639,7 +1699,7 @@ "message": "Автозаполнение последней использованной карты для текущего сайта" }, "commandAutofillIdentityDesc": { - "message": "Автозаполнение последней использованной личности для текущего сайта" + "message": "Автозаполнение последних использованных личных данных для текущего сайта" }, "commandGeneratePasswordDesc": { "message": "Сгенерировать и скопировать новый случайный пароль в буфер обмена." @@ -1797,7 +1857,7 @@ "message": "Полное имя" }, "identityName": { - "message": "Название личности" + "message": "Название личной информации" }, "company": { "message": "Компания" @@ -1929,7 +1989,7 @@ "message": "Карты" }, "identities": { - "message": "Личные данные" + "message": "Личная информация" }, "logins": { "message": "Логины" @@ -2144,6 +2204,9 @@ "useThisPassword": { "message": "Использовать этот пароль" }, + "useThisPassphrase": { + "message": "Использовать эту парольную фразу" + }, "useThisUsername": { "message": "Использовать это имя пользователя" }, @@ -2167,15 +2230,6 @@ "vaultTimeoutAction1": { "message": "Тайм-аут действия" }, - "newCustomizationOptionsCalloutTitle": { - "message": "Новые возможности настроек" - }, - "newCustomizationOptionsCalloutContent": { - "message": "Настройте работу с хранилищем с помощью действий быстрого копирования, компактного режима и многого другого!" - }, - "newCustomizationOptionsCalloutLink": { - "message": "Посмотреть все настройки внешнего вида" - }, "lock": { "message": "Блокировка", "description": "Verb form: to make secure or inaccessible by" @@ -2623,6 +2677,10 @@ "message": "Все Send’ы", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, + "maxAccessCountReached": { + "message": "Достигнут максимум обращений", + "description": "This text will be displayed after a Send has been accessed the maximum amount of times." + }, "hideTextByDefault": { "message": "Скрыть текст по умолчанию" }, @@ -2963,14 +3021,14 @@ "copyCustomFieldNameNotUnique": { "message": "Уникальный идентификатор не найден." }, - "convertOrganizationEncryptionDesc": { - "message": "$ORGANIZATION$ использует SSO с собственным сервером ключей. Для авторизации пользователям этой организации больше не требуется мастер-пароль.", - "placeholders": { - "organization": { - "content": "$1", - "example": "My Org Name" - } - } + "removeMasterPasswordForOrganizationUserKeyConnector": { + "message": "Мастер-пароль больше не требуется для членов следующей организации. Пожалуйста, подтвердите указанный ниже домен у администратора вашей организации." + }, + "organizationName": { + "message": "Название организации" + }, + "keyConnectorDomain": { + "message": "Домен соединителя ключей" }, "leaveOrganization": { "message": "Покинуть организацию" @@ -3532,12 +3590,36 @@ "deviceTrusted": { "message": "Доверенное устройство" }, - "sendsNoItemsTitle": { - "message": "Нет активных Send", + "trustOrganization": { + "message": "Доверенная организация" + }, + "trust": { + "message": "Доверять" + }, + "doNotTrust": { + "message": "Не доверять" + }, + "organizationNotTrusted": { + "message": "Организации не доверяют" + }, + "emergencyAccessTrustWarning": { + "message": "В целях обеспечения безопасности вашего аккаунта подтверждайте только в том случае, если вы предоставили этому пользователю экстренный доступ и его отпечаток совпадает с отображаемым в его аккаунте" + }, + "orgTrustWarning": { + "message": "В целях обеспечения безопасности вашего аккаунта продолжайте только в том случае, если вы являетесь членом этой организации, у вас включено восстановление аккаунта, а отображаемый ниже отпечаток совпадает с отпечатком организации." + }, + "orgTrustWarning1": { + "message": "В этой организации действует политика, которая позволит вам участвовать в восстановлении аккаунта. Регистрация позволит администраторам организации изменить ваш пароль. Продолжайте, только если вы знаете эту организацию и фраза отпечатков, показанная ниже, совпадает с отпечатками организации." + }, + "trustUser": { + "message": "Доверенный пользователь" + }, + "sendsTitleNoItems": { + "message": "Безопасная отправка конфиденциальной информации", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, - "sendsNoItemsMessage": { - "message": "Используйте Send для безопасного обмена зашифрованной информацией с кем угодно.", + "sendsBodyNoItems": { + "message": "Безопасно обменивайтесь файлами и данными с кем угодно на любой платформе. Ваша информация надежно шифруется и доступ к ней ограничен.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "inputRequired": { @@ -3750,11 +3832,11 @@ "description": "Screen reader text (aria-label) for new card button within inline menu" }, "newIdentity": { - "message": "Новая личность", + "message": "Новая личная информация", "description": "Button text to display within inline menu when there are no matching items on an identity field" }, "addNewIdentityItemAria": { - "message": "Добавление новой личности в хранилище, откроется в новом окне", + "message": "Добавление новой личной информации в хранилище, откроется в новом окне", "description": "Screen reader text (aria-label) for new identity button within inline menu" }, "bitwardenOverlayMenuAvailable": { @@ -3993,7 +4075,7 @@ "message": "Функция пока не поддерживается" }, "yourPasskeyIsLocked": { - "message": "Для использования passkey необходима аутентификация. Для продолжения работы подтвердите свою личность." + "message": "Для использования passkey необходима аутентификация. Для продолжения работы подтвердите вашу личность." }, "multifactorAuthenticationCancelled": { "message": "Многофакторная аутентификация отменена" @@ -4456,6 +4538,33 @@ } } }, + "downloadBitwarden": { + "message": "Скачать Bitwarden" + }, + "downloadBitwardenOnAllDevices": { + "message": "Скачать Bitwarden на все устройства" + }, + "getTheMobileApp": { + "message": "Скачать мобильное приложение" + }, + "getTheMobileAppDesc": { + "message": "Доступ к вашим паролям через мобильное приложение Bitwarden." + }, + "getTheDesktopApp": { + "message": "Скачать приложение для компьютера" + }, + "getTheDesktopAppDesc": { + "message": "Получите доступ к хранилищу без браузера, затем настройте разблокировку с помощью биометрии для ускорения разблокировки в приложении для компьютера и расширении браузера." + }, + "downloadFromBitwardenNow": { + "message": "Скачать с bitwarden.com" + }, + "getItOnGooglePlay": { + "message": "Получить на Google Play" + }, + "downloadOnTheAppStore": { + "message": "Скачать из App Store" + }, "permanentlyDeleteAttachmentConfirmation": { "message": "Вы уверены, что хотите навсегда удалить это вложение?" }, @@ -4917,6 +5026,15 @@ "biometricsStatusHelptextUnavailableReasonUnknown": { "message": "Биометрическая разблокировка в настоящее время недоступна по неизвестной причине." }, + "unlockVault": { + "message": "Разблокируйте свое хранилище за считанные секунды" + }, + "unlockVaultDesc": { + "message": "Вы можете настроить параметры разблокировки и тайм-аута для более быстрого доступа к хранилищу." + }, + "unlockPinSet": { + "message": "Установить PIN--код разблокировки" + }, "authenticating": { "message": "Аутентификация" }, @@ -4928,8 +5046,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": { @@ -5079,42 +5197,6 @@ "beta": { "message": "Beta" }, - "importantNotice": { - "message": "Важное уведомление" - }, - "setupTwoStepLogin": { - "message": "Настроить двухэтапную аутентификацию" - }, - "newDeviceVerificationNoticeContentPage1": { - "message": "Начиная с февраля 2025 года Bitwarden будет отправлять код на электронную почту вашего аккаунта для подтверждения авторизации с новых устройств." - }, - "newDeviceVerificationNoticeContentPage2": { - "message": "В качестве альтернативного способа защиты учетной записи вы можете настроить двухэтапную аутентификацию или сменить электронную почту на ту, к которой вы можете получить доступ." - }, - "remindMeLater": { - "message": "Напомнить позже" - }, - "newDeviceVerificationNoticePageOneFormContent": { - "message": "Есть ли у вас надежный доступ к электронной почте $EMAIL$?", - "placeholders": { - "email": { - "content": "$1", - "example": "your_name@email.com" - } - } - }, - "newDeviceVerificationNoticePageOneEmailAccessNo": { - "message": "Нет, не знаю" - }, - "newDeviceVerificationNoticePageOneEmailAccessYes": { - "message": "Да, я имею надежный доступ к своей электронной почте" - }, - "turnOnTwoStepLogin": { - "message": "Включить двухэтапную аутентификацию" - }, - "changeAcctEmail": { - "message": "Изменить email аккаунта" - }, "extensionWidth": { "message": "Ширина расширения" }, @@ -5169,6 +5251,12 @@ "changeAtRiskPassword": { "message": "Изменить пароль, подверженный риску" }, + "settingsVaultOptions": { + "message": "Настройки хранилища" + }, + "emptyVaultDescription": { + "message": "Хранилище защищает не только ваши пароли. Логины, идентификаторы, карты и заметки в нем надежно защищены." + }, "introCarouselLabel": { "message": "Добро пожаловать в Bitwarden" }, @@ -5176,7 +5264,7 @@ "message": "Безопасность, приоритет" }, "securityPrioritizedBody": { - "message": "Сохраняйте логины, карты и личные данные в своем защищенном хранилище. Bitwarden использует сквозное шифрование, чтобы защитить то, что для вас важно." + "message": "Сохраняйте логины, карты и личную информацию в своем защищенном хранилище. Bitwarden использует сквозное шифрование, чтобы защитить то, что для вас важно." }, "quickLogin": { "message": "Быстрая и простая авторизация" @@ -5195,5 +5283,98 @@ }, "secureDevicesBody": { "message": "Сохраняйте неограниченное количество паролей на неограниченном количестве устройств с помощью мобильных, браузерных и десктопных приложений Bitwarden." + }, + "nudgeBadgeAria": { + "message": "1 уведомление" + }, + "emptyVaultNudgeTitle": { + "message": "Импорт существующих паролей" + }, + "emptyVaultNudgeBody": { + "message": "Используйте импортер, чтобы быстро перенести логины в Bitwarden без их ручного добавления." + }, + "emptyVaultNudgeButton": { + "message": "Импортировать сейчас" + }, + "hasItemsVaultNudgeTitle": { + "message": "Добро пожаловать в ваше хранилище!" + }, + "hasItemsVaultNudgeBodyOne": { + "message": "Автозаполнение элементов на текущей странице" + }, + "hasItemsVaultNudgeBodyTwo": { + "message": "Избранные элементы для легкого доступа" + }, + "hasItemsVaultNudgeBodyThree": { + "message": "Ищите в своем хранилище все, что хотите" + }, + "newLoginNudgeTitle": { + "message": "Экономьте время с помощью автозаполнения" + }, + "newLoginNudgeBodyOne": { + "message": "Включите", + "description": "This is in multiple parts to allow for bold text in the middle of the sentence.", + "example": "Include a Website so this login appears as an autofill suggestion." + }, + "newLoginNudgeBodyBold": { + "message": "сайт", + "description": "This is in multiple parts to allow for bold text in the middle of the sentence.", + "example": "Include a Website so this login appears as an autofill suggestion." + }, + "newLoginNudgeBodyTwo": { + "message": "чтобы этот логин отображался в качестве предложения для автозаполнения.", + "description": "This is in multiple parts to allow for bold text in the middle of the sentence.", + "example": "Include a Website so this login appears as an autofill suggestion." + }, + "newCardNudgeTitle": { + "message": "Оформление заказа через интернет" + }, + "newCardNudgeBody": { + "message": "С помощью карт можно легко и безопасно автоматически заполнять формы оплаты." + }, + "newIdentityNudgeTitle": { + "message": "Упрощение создания аккаунтов" + }, + "newIdentityNudgeBody": { + "message": "С помощью личной информации можно быстро заполнять длинные регистрационные или контактные формы." + }, + "newNoteNudgeTitle": { + "message": "Храните ваши конфиденциальные данные в безопасности" + }, + "newNoteNudgeBody": { + "message": "С помощью заметок можно надежно хранить конфиденциальные данные, например, банковские или страховые реквизиты." + }, + "newSshNudgeTitle": { + "message": "Удобный для разработчиков SSH-доступ" + }, + "newSshNudgeBodyOne": { + "message": "Храните свои ключи и подключайтесь с помощью агента SSH для быстрой и зашифрованной аутентификации.", + "description": "Two part message", + "example": "Store your keys and connect with the SSH agent for fast, encrypted authentication. Learn more about SSH agent" + }, + "newSshNudgeBodyTwo": { + "message": "Узнайте больше об агенте SSH", + "description": "Two part message", + "example": "Store your keys and connect with the SSH agent for fast, encrypted authentication. Learn more about SSH agent" + }, + "generatorNudgeTitle": { + "message": "Быстрое создание паролей" + }, + "generatorNudgeBodyOne": { + "message": "Легко создавайте надежные и уникальные пароли, нажатием на кнопку,", + "description": "Two part message", + "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." + }, + "generatorNudgeBodyTwo": { + "message": "чтобы обеспечить безопасность ваших логинов.", + "description": "Two part message", + "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." + }, + "generatorNudgeBodyAria": { + "message": "Легко создавайте надежные и уникальные пароли, нажатием на кнопку 'Сгенерировать пароль', чтобы обеспечить безопасность ваших логинов.", + "description": "Aria label for the body content of the generator nudge" + }, + "noPermissionsViewPage": { + "message": "У вас нет прав для просмотра этой страницы. Попробуйте авторизоваться под другим аккаунтом." } } diff --git a/apps/browser/src/_locales/si/messages.json b/apps/browser/src/_locales/si/messages.json index 4efbb2dceeb..7832a96efc6 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,55 @@ "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.", + "notificationNewItemAria": { + "message": "New Item, opens in new window", + "description": "Aria label for the new item button in notification bar confirmation message when error is prompted" + }, + "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": { - "content": "$1" + "label": { + "content": "$1", + "example": "Login" } - }, - "description": "Shown to user after login is updated." + } + }, + "notificationLoginSaveConfirmation": { + "message": "saved to Bitwarden.", + "description": "Shown to user after item is saved." + }, + "notificationLoginUpdatedConfirmation": { + "message": "updated in Bitwarden.", + "description": "Shown to user after item is updated." + }, + "selectItemAriaLabel": { + "message": "Select $ITEMTYPE$, $ITEMNAME$", + "description": "Used by screen readers. $1 is the item type (like vault or folder), $2 is the selected item name.", + "placeholders": { + "itemType": { + "content": "$1" + }, + "itemName": { + "content": "$2" + } + } }, "saveAsNewLoginAction": { "message": "Save as new login", @@ -1082,12 +1120,16 @@ "message": "Update login", "description": "Button text for updating an existing login entry." }, - "saveLoginPrompt": { - "message": "Save login?", + "unlockToSave": { + "message": "Unlock to save this login", + "description": "User prompt to take action in order to save the login they just entered." + }, + "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": { @@ -1323,8 +1365,8 @@ "featureUnavailable": { "message": "විශේෂාංගය ලබාගත නොහැක" }, - "encryptionKeyMigrationRequired": { - "message": "Encryption key migration required. Please login through the web vault to update your encryption key." + "legacyEncryptionUnsupported": { + "message": "Legacy encryption is no longer supported. Please contact support to recover your account." }, "premiumMembership": { "message": "වාරික සාමාජිකත්වය" @@ -1557,6 +1599,24 @@ "autofillSuggestionsSectionTitle": { "message": "Autofill suggestions" }, + "autofillSpotlightTitle": { + "message": "Easily find autofill suggestions" + }, + "autofillSpotlightDesc": { + "message": "Turn off your browser's autofill settings, so they don't conflict with Bitwarden." + }, + "turnOffBrowserAutofill": { + "message": "Turn off $BROWSER$ autofill", + "placeholders": { + "browser": { + "content": "$1", + "example": "Chrome" + } + } + }, + "turnOffAutofill": { + "message": "Turn off autofill" + }, "showInlineMenuLabel": { "message": "Show autofill suggestions on form fields" }, @@ -2144,6 +2204,9 @@ "useThisPassword": { "message": "Use this password" }, + "useThisPassphrase": { + "message": "Use this passphrase" + }, "useThisUsername": { "message": "Use this username" }, @@ -2167,15 +2230,6 @@ "vaultTimeoutAction1": { "message": "Timeout action" }, - "newCustomizationOptionsCalloutTitle": { - "message": "New customization options" - }, - "newCustomizationOptionsCalloutContent": { - "message": "Customize your vault experience with quick copy actions, compact mode, and more!" - }, - "newCustomizationOptionsCalloutLink": { - "message": "View all Appearance settings" - }, "lock": { "message": "අගුල", "description": "Verb form: to make secure or inaccessible by" @@ -2623,6 +2677,10 @@ "message": "සියලු යවයි", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, + "maxAccessCountReached": { + "message": "Max access count reached", + "description": "This text will be displayed after a Send has been accessed the maximum amount of times." + }, "hideTextByDefault": { "message": "Hide text by default" }, @@ -2963,14 +3021,14 @@ "copyCustomFieldNameNotUnique": { "message": "අද්විතීය හඳුනාගැනීමක් සොයාගත නොහැකි විය." }, - "convertOrganizationEncryptionDesc": { - "message": "$ORGANIZATION$ ස්වයං සත්කාරක යතුරු සේවාදායකයක් සමඟ SSO භාවිතා කරයි. මෙම සංවිධානයේ සාමාජිකයන් සඳහා ප්රවිෂ්ට වීමට ප්රධාන මුරපදයක් තවදුරටත් අවශ්ය නොවේ.", - "placeholders": { - "organization": { - "content": "$1", - "example": "My Org Name" - } - } + "removeMasterPasswordForOrganizationUserKeyConnector": { + "message": "A master password is no longer required for members of the following organization. Please confirm the domain below with your organization administrator." + }, + "organizationName": { + "message": "Organization name" + }, + "keyConnectorDomain": { + "message": "Key Connector domain" }, "leaveOrganization": { "message": "සංවිධානය හැරයන්න" @@ -3532,12 +3590,36 @@ "deviceTrusted": { "message": "Device trusted" }, - "sendsNoItemsTitle": { - "message": "No active Sends", + "trustOrganization": { + "message": "Trust organization" + }, + "trust": { + "message": "Trust" + }, + "doNotTrust": { + "message": "Do not trust" + }, + "organizationNotTrusted": { + "message": "Organization is not trusted" + }, + "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" + }, + "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." + }, + "orgTrustWarning1": { + "message": "This organization has an Enterprise policy that will enroll you in account recovery. Enrollment will allow organization administrators to change your password. Only proceed if you recognize this organization and the fingerprint phrase displayed below matches the organization's fingerprint." + }, + "trustUser": { + "message": "Trust user" + }, + "sendsTitleNoItems": { + "message": "Send sensitive information safely", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, - "sendsNoItemsMessage": { - "message": "Use Send to securely share encrypted information with anyone.", + "sendsBodyNoItems": { + "message": "Share files and data securely with anyone, on any platform. Your information will remain end-to-end encrypted while limiting exposure.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "inputRequired": { @@ -4456,6 +4538,33 @@ } } }, + "downloadBitwarden": { + "message": "Download Bitwarden" + }, + "downloadBitwardenOnAllDevices": { + "message": "Download Bitwarden on all devices" + }, + "getTheMobileApp": { + "message": "Get the mobile app" + }, + "getTheMobileAppDesc": { + "message": "Access your passwords on the go with the Bitwarden mobile app." + }, + "getTheDesktopApp": { + "message": "Get the desktop app" + }, + "getTheDesktopAppDesc": { + "message": "Access your vault without a browser, then set up unlock with biometrics to expedite unlocking in both the desktop app and browser extension." + }, + "downloadFromBitwardenNow": { + "message": "Download from bitwarden.com now" + }, + "getItOnGooglePlay": { + "message": "Get it on Google Play" + }, + "downloadOnTheAppStore": { + "message": "Download on the App Store" + }, "permanentlyDeleteAttachmentConfirmation": { "message": "Are you sure you want to permanently delete this attachment?" }, @@ -4917,6 +5026,15 @@ "biometricsStatusHelptextUnavailableReasonUnknown": { "message": "Biometric unlock is currently unavailable for an unknown reason." }, + "unlockVault": { + "message": "Unlock your vault in seconds" + }, + "unlockVaultDesc": { + "message": "You can customize your unlock and timeout settings to more quickly access your vault." + }, + "unlockPinSet": { + "message": "Unlock PIN set" + }, "authenticating": { "message": "Authenticating" }, @@ -4928,8 +5046,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": { @@ -5079,42 +5197,6 @@ "beta": { "message": "Beta" }, - "importantNotice": { - "message": "Important notice" - }, - "setupTwoStepLogin": { - "message": "Set up two-step login" - }, - "newDeviceVerificationNoticeContentPage1": { - "message": "Bitwarden will send a code to your account email to verify logins from new devices starting in February 2025." - }, - "newDeviceVerificationNoticeContentPage2": { - "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" - }, - "newDeviceVerificationNoticePageOneFormContent": { - "message": "Do you have reliable access to your email, $EMAIL$?", - "placeholders": { - "email": { - "content": "$1", - "example": "your_name@email.com" - } - } - }, - "newDeviceVerificationNoticePageOneEmailAccessNo": { - "message": "No, I do not" - }, - "newDeviceVerificationNoticePageOneEmailAccessYes": { - "message": "Yes, I can reliably access my email" - }, - "turnOnTwoStepLogin": { - "message": "Turn on two-step login" - }, - "changeAcctEmail": { - "message": "Change account email" - }, "extensionWidth": { "message": "Extension width" }, @@ -5169,6 +5251,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 +5283,98 @@ }, "secureDevicesBody": { "message": "Save unlimited passwords across unlimited devices with Bitwarden mobile, browser, and desktop apps." + }, + "nudgeBadgeAria": { + "message": "1 notification" + }, + "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!" + }, + "hasItemsVaultNudgeBodyOne": { + "message": "Autofill items for the current page" + }, + "hasItemsVaultNudgeBodyTwo": { + "message": "Favorite items for easy access" + }, + "hasItemsVaultNudgeBodyThree": { + "message": "Search your vault for something else" + }, + "newLoginNudgeTitle": { + "message": "Save time with autofill" + }, + "newLoginNudgeBodyOne": { + "message": "Include a", + "description": "This is in multiple parts to allow for bold text in the middle of the sentence.", + "example": "Include a Website so this login appears as an autofill suggestion." + }, + "newLoginNudgeBodyBold": { + "message": "Website", + "description": "This is in multiple parts to allow for bold text in the middle of the sentence.", + "example": "Include a Website so this login appears as an autofill suggestion." + }, + "newLoginNudgeBodyTwo": { + "message": "so this login appears as an autofill suggestion.", + "description": "This is in multiple parts to allow for bold text in the middle of the sentence.", + "example": "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" + }, + "newSshNudgeBodyOne": { + "message": "Store your keys and connect with the SSH agent for fast, encrypted authentication.", + "description": "Two part message", + "example": "Store your keys and connect with the SSH agent for fast, encrypted authentication. Learn more about SSH agent" + }, + "newSshNudgeBodyTwo": { + "message": "Learn more about SSH agent", + "description": "Two part message", + "example": "Store your keys and connect with the SSH agent for fast, encrypted authentication. Learn more about SSH agent" + }, + "generatorNudgeTitle": { + "message": "Quickly create passwords" + }, + "generatorNudgeBodyOne": { + "message": "Easily create strong and unique passwords by clicking on", + "description": "Two part message", + "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." + }, + "generatorNudgeBodyTwo": { + "message": "to help you keep your logins secure.", + "description": "Two part message", + "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." + }, + "generatorNudgeBodyAria": { + "message": "Easily create strong and unique passwords by clicking on the Generate password button to help you keep your logins secure.", + "description": "Aria label for the body content of the generator nudge" + }, + "noPermissionsViewPage": { + "message": "You do not have permissions to view this page. Try logging in with a different account." } } diff --git a/apps/browser/src/_locales/sk/messages.json b/apps/browser/src/_locales/sk/messages.json index b427e902334..eac6179685b 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)" @@ -471,7 +474,7 @@ "message": "Používateľské meno vygenerované" }, "emailGenerated": { - "message": "E-mail vygenoravný" + "message": "E-mail vygenerovaný" }, "regeneratePassword": { "message": "Vygenerovať nové heslo" @@ -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,55 @@ "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.", + "notificationNewItemAria": { + "message": "Nová položka, otvorí sa v novom okne", + "description": "Aria label for the new item button in notification bar confirmation message when error is prompted" + }, + "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": { - "content": "$1" + "label": { + "content": "$1", + "example": "Login" } - }, - "description": "Shown to user after login is updated." + } + }, + "notificationLoginSaveConfirmation": { + "message": "uložené do Bitwardenu.", + "description": "Shown to user after item is saved." + }, + "notificationLoginUpdatedConfirmation": { + "message": "aktualizované v Bitwardene.", + "description": "Shown to user after item is updated." + }, + "selectItemAriaLabel": { + "message": "Vybrať $ITEMTYPE$, $ITEMNAME$", + "description": "Used by screen readers. $1 is the item type (like vault or folder), $2 is the selected item name.", + "placeholders": { + "itemType": { + "content": "$1" + }, + "itemName": { + "content": "$2" + } + } }, "saveAsNewLoginAction": { "message": "Uložiť ako nové prihlasovacie údaje", @@ -1082,12 +1120,16 @@ "message": "Aktualizovať prihlasovacie údaje", "description": "Button text for updating an existing login entry." }, - "saveLoginPrompt": { - "message": "Uložiť prihlasovacie údaje?", + "unlockToSave": { + "message": "Odomknúť na uloženie prihlasovacích údajov", + "description": "User prompt to take action in order to save the login they just entered." + }, + "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": { @@ -1323,8 +1365,8 @@ "featureUnavailable": { "message": "Funkcia nie je k dispozícii" }, - "encryptionKeyMigrationRequired": { - "message": "Vyžaduje sa migrácia šifrovacieho kľúča. Na aktualizáciu šifrovacieho kľúča sa prihláste cez webový trezor." + "legacyEncryptionUnsupported": { + "message": "Staršie šifrovanie už nie je podporované. Ak chcete obnoviť svoj účet, obráťte sa na podporu." }, "premiumMembership": { "message": "Prémiové členstvo" @@ -1557,6 +1599,24 @@ "autofillSuggestionsSectionTitle": { "message": "Návrhy automatického vypĺňania" }, + "autofillSpotlightTitle": { + "message": "Jednoduché vyhľadávanie návrhov na automatické vypĺňanie" + }, + "autofillSpotlightDesc": { + "message": "Vypnite nastavenia automatického vypĺňania v prehliadači, aby nedošlo ku konfliktu s Bitwardenom." + }, + "turnOffBrowserAutofill": { + "message": "Vypnúť automatické vypĺňanie v $BROWSER$", + "placeholders": { + "browser": { + "content": "$1", + "example": "Chrome" + } + } + }, + "turnOffAutofill": { + "message": "Vypnúť automatické vypĺňanie" + }, "showInlineMenuLabel": { "message": "Zobraziť návrhy automatického vypĺňania v poliach formulára" }, @@ -2144,6 +2204,9 @@ "useThisPassword": { "message": "Použiť toto heslo" }, + "useThisPassphrase": { + "message": "Použiť túto prístupovú frázu" + }, "useThisUsername": { "message": "Použiť toto používateľské meno" }, @@ -2167,15 +2230,6 @@ "vaultTimeoutAction1": { "message": "Akcia pri vypršaní časového limitu" }, - "newCustomizationOptionsCalloutTitle": { - "message": "Nové možnosti prispôsobenia" - }, - "newCustomizationOptionsCalloutContent": { - "message": "Prispôsobte si trezor pomocou akcií rýchleho kopírovania, kompaktného režimu a ďalších možností!" - }, - "newCustomizationOptionsCalloutLink": { - "message": "Zobraziť všetky nastavenia vzhľadu" - }, "lock": { "message": "Uzamknúť", "description": "Verb form: to make secure or inaccessible by" @@ -2623,6 +2677,10 @@ "message": "Všetky Sends", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, + "maxAccessCountReached": { + "message": "Bol dosiahnutý maximálny počet prístupov", + "description": "This text will be displayed after a Send has been accessed the maximum amount of times." + }, "hideTextByDefault": { "message": "V predvolenom nastavení skryť text" }, @@ -2715,7 +2773,7 @@ "message": "Nové heslo" }, "sendDisabled": { - "message": "Send zakázaný", + "message": "Send bol odstránený", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "sendDisabledWarning": { @@ -2963,14 +3021,14 @@ "copyCustomFieldNameNotUnique": { "message": "Nenašiel sa žiadny jedinečný identifikátor." }, - "convertOrganizationEncryptionDesc": { - "message": "$ORGANIZATION$ používa SSO s vlastným kľúčovým serverom. Na prihlásenie členov tejto organizácie už nie je potrebné hlavné heslo.", - "placeholders": { - "organization": { - "content": "$1", - "example": "My Org Name" - } - } + "removeMasterPasswordForOrganizationUserKeyConnector": { + "message": "Hlavné heslo sa už nevyžaduje pre členov tejto organizácie. Nižšie uvedenú doménu potvrďte u správcu organizácie." + }, + "organizationName": { + "message": "Názov organizácie" + }, + "keyConnectorDomain": { + "message": "Doména Key Connectora" }, "leaveOrganization": { "message": "Opustiť organizáciu" @@ -3532,12 +3590,36 @@ "deviceTrusted": { "message": "Dôveryhodné zariadenie" }, - "sendsNoItemsTitle": { - "message": "Žiadne aktívne Sendy", + "trustOrganization": { + "message": "Dôverovať organizácii" + }, + "trust": { + "message": "Dôverovať" + }, + "doNotTrust": { + "message": "Nedôverovať" + }, + "organizationNotTrusted": { + "message": "Organizácia nie je dôveryhodná" + }, + "emergencyAccessTrustWarning": { + "message": "Pre bezpečnosť vášho účtu stačí overiť, ze ste tomuto používateľovi udelili núdzový pristúp, a že odtlačok sa zhoduje s odtlačkom zobrazenom v používateľovom účte" + }, + "orgTrustWarning": { + "message": "Pre bezpečnosť vášho účtu pokračujte, iba ak ste členom organizácie, máte povolenú obnovu účtu a odtlačok sa zhoduje s odtlačkom organizácie." + }, + "orgTrustWarning1": { + "message": "Táto organizácia má pravidlá spoločnosti, ktoré vás zaregistrujú do obnovy účtu. Zápis umožní správcom organizácie zmeniť vaše heslo. Pokračujte len vtedy, ak túto organizáciu poznáte a nižšie zobrazená fráza odtlačku prsta sa zhoduje s odtlačkom prsta organizácie." + }, + "trustUser": { + "message": "Dôverovať používateľovi" + }, + "sendsTitleNoItems": { + "message": "Send, citlivé informácie bezpečne", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, - "sendsNoItemsMessage": { - "message": "Použite Send na bezpečné zdieľanie zašifrovaných informácii s kýmkoľvek.", + "sendsBodyNoItems": { + "message": "Bezpečne zdieľajte súbory a údaje s kýmkoľvek a na akejkoľvek platforme. Vaše informácie zostanú end-to-end zašifrované a zároveň sa obmedzí ich odhalenie.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "inputRequired": { @@ -4456,6 +4538,33 @@ } } }, + "downloadBitwarden": { + "message": "Stiahnuť Bitwarden" + }, + "downloadBitwardenOnAllDevices": { + "message": "Stiahnuť Bitwarden na všetky zariadenia" + }, + "getTheMobileApp": { + "message": "Získajte mobilnú aplikáciu" + }, + "getTheMobileAppDesc": { + "message": "Majte prístup k heslám na cestách pomocou mobilnej aplikácie Bitwarden." + }, + "getTheDesktopApp": { + "message": "Získať desktopovú aplikáciu" + }, + "getTheDesktopAppDesc": { + "message": "Pristupujte k trezoru bez prehliadača, a potom nastavte odomykanie pomocou biometrie, aby ste urýchlili odomykanie v desktopovej aplikácii aj v rozšírení prehliadača." + }, + "downloadFromBitwardenNow": { + "message": "Stiahnuť teraz z bitwarden.com" + }, + "getItOnGooglePlay": { + "message": "Získať z Google Play" + }, + "downloadOnTheAppStore": { + "message": "Stiahnuť z App Store" + }, "permanentlyDeleteAttachmentConfirmation": { "message": "Naozaj chcete natrvalo odstrániť túto prílohu?" }, @@ -4649,7 +4758,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" @@ -4917,6 +5026,15 @@ "biometricsStatusHelptextUnavailableReasonUnknown": { "message": "Odomykanie biometrickými údajmi je momentálne z neznámych dôvodov nedostupné." }, + "unlockVault": { + "message": "Odomknite trezor za pár sekúnd" + }, + "unlockVaultDesc": { + "message": "Pre rýchlejší prístup k trezoru si môžete upraviť nastavenia odomknutia a časový limit." + }, + "unlockPinSet": { + "message": "PIN na odomknutie nastavený" + }, "authenticating": { "message": "Overuje sa" }, @@ -4928,8 +5046,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": { @@ -5079,42 +5197,6 @@ "beta": { "message": "Beta" }, - "importantNotice": { - "message": "Dôležité upozornenie" - }, - "setupTwoStepLogin": { - "message": "Nastavenie dvojstupňového prihlásenia" - }, - "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í." - }, - "newDeviceVerificationNoticeContentPage2": { - "message": "Ako alternatívny spôsob ochrany svojho účtu môžete nastaviť dvojstupňové prihlásenie alebo zmeniť e-mail na taký, ku ktorému máte prístup." - }, - "remindMeLater": { - "message": "Pripomenúť neskôr" - }, - "newDeviceVerificationNoticePageOneFormContent": { - "message": "Máte zaručený prístup k e-mailu $EMAIL$?", - "placeholders": { - "email": { - "content": "$1", - "example": "your_name@email.com" - } - } - }, - "newDeviceVerificationNoticePageOneEmailAccessNo": { - "message": "Nie, nemám" - }, - "newDeviceVerificationNoticePageOneEmailAccessYes": { - "message": "Áno, mám zaručený prístup k e-mailu" - }, - "turnOnTwoStepLogin": { - "message": "Zapnúť dvojstupňové prihlásenie" - }, - "changeAcctEmail": { - "message": "Zmeniť e-mail účtu" - }, "extensionWidth": { "message": "Šírka rozšírenia" }, @@ -5169,6 +5251,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 +5283,98 @@ }, "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." + }, + "nudgeBadgeAria": { + "message": "1 upozornenie" + }, + "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!" + }, + "hasItemsVaultNudgeBodyOne": { + "message": "Položky na automatické vypĺňanie pre aktuálnu stránku" + }, + "hasItemsVaultNudgeBodyTwo": { + "message": "Obľúbené položky pre jednoduchý prístup" + }, + "hasItemsVaultNudgeBodyThree": { + "message": "Vyhľadajte v trezore niečo iné" + }, + "newLoginNudgeTitle": { + "message": "Ušetrite čas s automatickým vypĺňaním" + }, + "newLoginNudgeBodyOne": { + "message": "Zahrnúť", + "description": "This is in multiple parts to allow for bold text in the middle of the sentence.", + "example": "Include a Website so this login appears as an autofill suggestion." + }, + "newLoginNudgeBodyBold": { + "message": "webstránku", + "description": "This is in multiple parts to allow for bold text in the middle of the sentence.", + "example": "Include a Website so this login appears as an autofill suggestion." + }, + "newLoginNudgeBodyTwo": { + "message": "aby sa tieto prihlasovacie údaje zobrazili ako návrh na automatické vyplnenie.", + "description": "This is in multiple parts to allow for bold text in the middle of the sentence.", + "example": "Include a Website so this login appears as an autofill suggestion." + }, + "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" + }, + "newSshNudgeBodyOne": { + "message": "Uložte si kľúče a pripojte sa pomocou agenta SSH na rýchle šifrované overovanie.", + "description": "Two part message", + "example": "Store your keys and connect with the SSH agent for fast, encrypted authentication. Learn more about SSH agent" + }, + "newSshNudgeBodyTwo": { + "message": "Viac informácií o agentovi SSH", + "description": "Two part message", + "example": "Store your keys and connect with the SSH agent for fast, encrypted authentication. Learn more about SSH agent" + }, + "generatorNudgeTitle": { + "message": "Rýchle vytváranie hesiel" + }, + "generatorNudgeBodyOne": { + "message": "Jednoducho vytvorte silné a jedinečné heslá kliknutím na", + "description": "Two part message", + "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." + }, + "generatorNudgeBodyTwo": { + "message": "aby ste mohli ochrániť prihlasovacie údaje.", + "description": "Two part message", + "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." + }, + "generatorNudgeBodyAria": { + "message": "Jednoducho vytvorte silné a jedinečné heslá kliknutím na tlačidlo Generovať heslo, aby ste zabezpečili prihlasovacie údaje.", + "description": "Aria label for the body content of the generator nudge" + }, + "noPermissionsViewPage": { + "message": "Nemáte oprávnenie na zobrazenie tejto stránky. Skúste sa prihlásiť pomocou iného účtu." } } diff --git a/apps/browser/src/_locales/sl/messages.json b/apps/browser/src/_locales/sl/messages.json index 61a70650d16..c42b4a3a2ba 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,55 @@ "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.", + "notificationNewItemAria": { + "message": "New Item, opens in new window", + "description": "Aria label for the new item button in notification bar confirmation message when error is prompted" + }, + "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": { - "content": "$1" + "label": { + "content": "$1", + "example": "Login" } - }, - "description": "Shown to user after login is updated." + } + }, + "notificationLoginSaveConfirmation": { + "message": "saved to Bitwarden.", + "description": "Shown to user after item is saved." + }, + "notificationLoginUpdatedConfirmation": { + "message": "updated in Bitwarden.", + "description": "Shown to user after item is updated." + }, + "selectItemAriaLabel": { + "message": "Select $ITEMTYPE$, $ITEMNAME$", + "description": "Used by screen readers. $1 is the item type (like vault or folder), $2 is the selected item name.", + "placeholders": { + "itemType": { + "content": "$1" + }, + "itemName": { + "content": "$2" + } + } }, "saveAsNewLoginAction": { "message": "Save as new login", @@ -1082,12 +1120,16 @@ "message": "Update login", "description": "Button text for updating an existing login entry." }, - "saveLoginPrompt": { - "message": "Save login?", + "unlockToSave": { + "message": "Unlock to save this login", + "description": "User prompt to take action in order to save the login they just entered." + }, + "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": { @@ -1323,8 +1365,8 @@ "featureUnavailable": { "message": "Funkcija ni na voljo." }, - "encryptionKeyMigrationRequired": { - "message": "Encryption key migration required. Please login through the web vault to update your encryption key." + "legacyEncryptionUnsupported": { + "message": "Legacy encryption is no longer supported. Please contact support to recover your account." }, "premiumMembership": { "message": "Premium članstvo" @@ -1557,6 +1599,24 @@ "autofillSuggestionsSectionTitle": { "message": "Autofill suggestions" }, + "autofillSpotlightTitle": { + "message": "Easily find autofill suggestions" + }, + "autofillSpotlightDesc": { + "message": "Turn off your browser's autofill settings, so they don't conflict with Bitwarden." + }, + "turnOffBrowserAutofill": { + "message": "Turn off $BROWSER$ autofill", + "placeholders": { + "browser": { + "content": "$1", + "example": "Chrome" + } + } + }, + "turnOffAutofill": { + "message": "Turn off autofill" + }, "showInlineMenuLabel": { "message": "Show autofill suggestions on form fields" }, @@ -2144,6 +2204,9 @@ "useThisPassword": { "message": "Use this password" }, + "useThisPassphrase": { + "message": "Use this passphrase" + }, "useThisUsername": { "message": "Use this username" }, @@ -2167,15 +2230,6 @@ "vaultTimeoutAction1": { "message": "Timeout action" }, - "newCustomizationOptionsCalloutTitle": { - "message": "New customization options" - }, - "newCustomizationOptionsCalloutContent": { - "message": "Customize your vault experience with quick copy actions, compact mode, and more!" - }, - "newCustomizationOptionsCalloutLink": { - "message": "View all Appearance settings" - }, "lock": { "message": "Zaklepanje", "description": "Verb form: to make secure or inaccessible by" @@ -2623,6 +2677,10 @@ "message": "Vse pošiljke", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, + "maxAccessCountReached": { + "message": "Max access count reached", + "description": "This text will be displayed after a Send has been accessed the maximum amount of times." + }, "hideTextByDefault": { "message": "Hide text by default" }, @@ -2963,14 +3021,14 @@ "copyCustomFieldNameNotUnique": { "message": "No unique identifier found." }, - "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.", - "placeholders": { - "organization": { - "content": "$1", - "example": "My Org Name" - } - } + "removeMasterPasswordForOrganizationUserKeyConnector": { + "message": "A master password is no longer required for members of the following organization. Please confirm the domain below with your organization administrator." + }, + "organizationName": { + "message": "Organization name" + }, + "keyConnectorDomain": { + "message": "Key Connector domain" }, "leaveOrganization": { "message": "Leave organization" @@ -3532,12 +3590,36 @@ "deviceTrusted": { "message": "Device trusted" }, - "sendsNoItemsTitle": { - "message": "No active Sends", + "trustOrganization": { + "message": "Trust organization" + }, + "trust": { + "message": "Trust" + }, + "doNotTrust": { + "message": "Do not trust" + }, + "organizationNotTrusted": { + "message": "Organization is not trusted" + }, + "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" + }, + "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." + }, + "orgTrustWarning1": { + "message": "This organization has an Enterprise policy that will enroll you in account recovery. Enrollment will allow organization administrators to change your password. Only proceed if you recognize this organization and the fingerprint phrase displayed below matches the organization's fingerprint." + }, + "trustUser": { + "message": "Trust user" + }, + "sendsTitleNoItems": { + "message": "Send sensitive information safely", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, - "sendsNoItemsMessage": { - "message": "Use Send to securely share encrypted information with anyone.", + "sendsBodyNoItems": { + "message": "Share files and data securely with anyone, on any platform. Your information will remain end-to-end encrypted while limiting exposure.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "inputRequired": { @@ -4456,6 +4538,33 @@ } } }, + "downloadBitwarden": { + "message": "Download Bitwarden" + }, + "downloadBitwardenOnAllDevices": { + "message": "Download Bitwarden on all devices" + }, + "getTheMobileApp": { + "message": "Get the mobile app" + }, + "getTheMobileAppDesc": { + "message": "Access your passwords on the go with the Bitwarden mobile app." + }, + "getTheDesktopApp": { + "message": "Get the desktop app" + }, + "getTheDesktopAppDesc": { + "message": "Access your vault without a browser, then set up unlock with biometrics to expedite unlocking in both the desktop app and browser extension." + }, + "downloadFromBitwardenNow": { + "message": "Download from bitwarden.com now" + }, + "getItOnGooglePlay": { + "message": "Get it on Google Play" + }, + "downloadOnTheAppStore": { + "message": "Download on the App Store" + }, "permanentlyDeleteAttachmentConfirmation": { "message": "Are you sure you want to permanently delete this attachment?" }, @@ -4917,6 +5026,15 @@ "biometricsStatusHelptextUnavailableReasonUnknown": { "message": "Biometric unlock is currently unavailable for an unknown reason." }, + "unlockVault": { + "message": "Unlock your vault in seconds" + }, + "unlockVaultDesc": { + "message": "You can customize your unlock and timeout settings to more quickly access your vault." + }, + "unlockPinSet": { + "message": "Unlock PIN set" + }, "authenticating": { "message": "Authenticating" }, @@ -4928,8 +5046,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": { @@ -5079,42 +5197,6 @@ "beta": { "message": "Beta" }, - "importantNotice": { - "message": "Important notice" - }, - "setupTwoStepLogin": { - "message": "Set up two-step login" - }, - "newDeviceVerificationNoticeContentPage1": { - "message": "Bitwarden will send a code to your account email to verify logins from new devices starting in February 2025." - }, - "newDeviceVerificationNoticeContentPage2": { - "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" - }, - "newDeviceVerificationNoticePageOneFormContent": { - "message": "Do you have reliable access to your email, $EMAIL$?", - "placeholders": { - "email": { - "content": "$1", - "example": "your_name@email.com" - } - } - }, - "newDeviceVerificationNoticePageOneEmailAccessNo": { - "message": "No, I do not" - }, - "newDeviceVerificationNoticePageOneEmailAccessYes": { - "message": "Yes, I can reliably access my email" - }, - "turnOnTwoStepLogin": { - "message": "Turn on two-step login" - }, - "changeAcctEmail": { - "message": "Change account email" - }, "extensionWidth": { "message": "Extension width" }, @@ -5169,6 +5251,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 +5283,98 @@ }, "secureDevicesBody": { "message": "Save unlimited passwords across unlimited devices with Bitwarden mobile, browser, and desktop apps." + }, + "nudgeBadgeAria": { + "message": "1 notification" + }, + "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!" + }, + "hasItemsVaultNudgeBodyOne": { + "message": "Autofill items for the current page" + }, + "hasItemsVaultNudgeBodyTwo": { + "message": "Favorite items for easy access" + }, + "hasItemsVaultNudgeBodyThree": { + "message": "Search your vault for something else" + }, + "newLoginNudgeTitle": { + "message": "Save time with autofill" + }, + "newLoginNudgeBodyOne": { + "message": "Include a", + "description": "This is in multiple parts to allow for bold text in the middle of the sentence.", + "example": "Include a Website so this login appears as an autofill suggestion." + }, + "newLoginNudgeBodyBold": { + "message": "Website", + "description": "This is in multiple parts to allow for bold text in the middle of the sentence.", + "example": "Include a Website so this login appears as an autofill suggestion." + }, + "newLoginNudgeBodyTwo": { + "message": "so this login appears as an autofill suggestion.", + "description": "This is in multiple parts to allow for bold text in the middle of the sentence.", + "example": "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" + }, + "newSshNudgeBodyOne": { + "message": "Store your keys and connect with the SSH agent for fast, encrypted authentication.", + "description": "Two part message", + "example": "Store your keys and connect with the SSH agent for fast, encrypted authentication. Learn more about SSH agent" + }, + "newSshNudgeBodyTwo": { + "message": "Learn more about SSH agent", + "description": "Two part message", + "example": "Store your keys and connect with the SSH agent for fast, encrypted authentication. Learn more about SSH agent" + }, + "generatorNudgeTitle": { + "message": "Quickly create passwords" + }, + "generatorNudgeBodyOne": { + "message": "Easily create strong and unique passwords by clicking on", + "description": "Two part message", + "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." + }, + "generatorNudgeBodyTwo": { + "message": "to help you keep your logins secure.", + "description": "Two part message", + "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." + }, + "generatorNudgeBodyAria": { + "message": "Easily create strong and unique passwords by clicking on the Generate password button to help you keep your logins secure.", + "description": "Aria label for the body content of the generator nudge" + }, + "noPermissionsViewPage": { + "message": "You do not have permissions to view this page. Try logging in with a different account." } } diff --git a/apps/browser/src/_locales/sr/messages.json b/apps/browser/src/_locales/sr/messages.json index a36006894ba..d0d76cba4cf 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,55 @@ "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.", + "notificationNewItemAria": { + "message": "Нова ставка, отвара се у новом прозору", + "description": "Aria label for the new item button in notification bar confirmation message when error is prompted" + }, + "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": { - "content": "$1" + "label": { + "content": "$1", + "example": "Login" } - }, - "description": "Shown to user after login is updated." + } + }, + "notificationLoginSaveConfirmation": { + "message": "сачувано у Bitwarden.", + "description": "Shown to user after item is saved." + }, + "notificationLoginUpdatedConfirmation": { + "message": "ажурирано у Bitwarden.", + "description": "Shown to user after item is updated." + }, + "selectItemAriaLabel": { + "message": "Одабрати $ITEMTYPE$, $ITEMNAME$", + "description": "Used by screen readers. $1 is the item type (like vault or folder), $2 is the selected item name.", + "placeholders": { + "itemType": { + "content": "$1" + }, + "itemName": { + "content": "$2" + } + } }, "saveAsNewLoginAction": { "message": "Сачувати као нову пријаву", @@ -1082,12 +1120,16 @@ "message": "Ажурирати пријаву", "description": "Button text for updating an existing login entry." }, - "saveLoginPrompt": { - "message": "Сачувати пријаву?", + "unlockToSave": { + "message": "Откључајте да бисте сачували ову пријаву", + "description": "User prompt to take action in order to save the login they just entered." + }, + "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 +1141,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 +1150,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 +1162,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": { @@ -1323,8 +1365,8 @@ "featureUnavailable": { "message": "Функција је недоступна" }, - "encryptionKeyMigrationRequired": { - "message": "Потребна је миграција кључа за шифровање. Пријавите се преко веб сефа да бисте ажурирали кључ за шифровање." + "legacyEncryptionUnsupported": { + "message": "Legacy encryption is no longer supported. Please contact support to recover your account." }, "premiumMembership": { "message": "Премијум чланство" @@ -1557,6 +1599,24 @@ "autofillSuggestionsSectionTitle": { "message": "Предлог за ауто-попуњавања" }, + "autofillSpotlightTitle": { + "message": "Једноставно пронађите предлоге за ауто-пуњење" + }, + "autofillSpotlightDesc": { + "message": "Искључите подешавања ауто-пуњења прегледача, тако да се не сукобљавај са Bitwarden." + }, + "turnOffBrowserAutofill": { + "message": "Turn off $BROWSER$ autofill", + "placeholders": { + "browser": { + "content": "$1", + "example": "Chrome" + } + } + }, + "turnOffAutofill": { + "message": "Turn off autofill" + }, "showInlineMenuLabel": { "message": "Прикажи предлоге за ауто-попуњавање у пољима обрасца" }, @@ -2144,6 +2204,9 @@ "useThisPassword": { "message": "Употреби ову лозинку" }, + "useThisPassphrase": { + "message": "Употреби ову приступну фразу" + }, "useThisUsername": { "message": "Употреби ово корисничко име" }, @@ -2167,15 +2230,6 @@ "vaultTimeoutAction1": { "message": "Акција тајмаута" }, - "newCustomizationOptionsCalloutTitle": { - "message": "Нове опције прилагођавања" - }, - "newCustomizationOptionsCalloutContent": { - "message": "Прилагодите своје искуство сефа помоћу брзих акција копирања, компактног режима и још много тога!" - }, - "newCustomizationOptionsCalloutLink": { - "message": "Погледајте сва подешавања изглед" - }, "lock": { "message": "Закључај", "description": "Verb form: to make secure or inaccessible by" @@ -2323,7 +2377,7 @@ "message": "Политика приватности" }, "yourNewPasswordCannotBeTheSameAsYourCurrentPassword": { - "message": "Your new password cannot be the same as your current password." + "message": "Ваша нова лозинка не може бити иста као тренутна лозинка." }, "hintEqualsPassword": { "message": "Ваш савет за лозинку не може да буде исти као лозинка." @@ -2623,6 +2677,10 @@ "message": "Све „Send“", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, + "maxAccessCountReached": { + "message": "Достигнут максималан број приступа", + "description": "This text will be displayed after a Send has been accessed the maximum amount of times." + }, "hideTextByDefault": { "message": "Сакриј текст подразумевано" }, @@ -2963,14 +3021,14 @@ "copyCustomFieldNameNotUnique": { "message": "Није пронађен ниједан јединствени идентификатор." }, - "convertOrganizationEncryptionDesc": { - "message": "$ORGANIZATION$ користи SSO уз сопствени сервер за кључеве. Главна лозинка за пријаву више није неопходна за чланове ове организације.", - "placeholders": { - "organization": { - "content": "$1", - "example": "My Org Name" - } - } + "removeMasterPasswordForOrganizationUserKeyConnector": { + "message": "Главна лозинка више није потребна за чланове следеће организације. Молимо потврдите домен са администратором организације." + }, + "organizationName": { + "message": "Назив организације" + }, + "keyConnectorDomain": { + "message": "Домен конектора кључа" }, "leaveOrganization": { "message": "Напусти организацију" @@ -3532,12 +3590,36 @@ "deviceTrusted": { "message": "Уређај поуздан" }, - "sendsNoItemsTitle": { - "message": "Нема активних Sends", + "trustOrganization": { + "message": "Повери организацију" + }, + "trust": { + "message": "Повери" + }, + "doNotTrust": { + "message": "Не повери" + }, + "organizationNotTrusted": { + "message": "Организација није поверљива" + }, + "emergencyAccessTrustWarning": { + "message": "За сигурност вашег налога, потврдите само ако сте добили хитни приступ овом кориснику и њиховим отискама одговарају оно што се приказује на њиховом налогу" + }, + "orgTrustWarning": { + "message": "За сигурност вашег рачуна, наставите само ако сте члан ове организације, омогућили опоравак рачуна и отисак који се приказује испод одговара прстима организације." + }, + "orgTrustWarning1": { + "message": "This organization has an Enterprise policy that will enroll you in account recovery. Enrollment will allow organization administrators to change your password. Only proceed if you recognize this organization and the fingerprint phrase displayed below matches the organization's fingerprint." + }, + "trustUser": { + "message": "Повери кориснику" + }, + "sendsTitleNoItems": { + "message": "Шаљите безбедно осетљиве информације", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, - "sendsNoItemsMessage": { - "message": "Употребите Send да безбедно делите шифроване информације са било ким.", + "sendsBodyNoItems": { + "message": "Делите датотеке и податке безбедно са било ким, на било којој платформи. Ваше информације ће остати шифроване од почетка-до-краја уз ограничење изложености.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "inputRequired": { @@ -4456,6 +4538,33 @@ } } }, + "downloadBitwarden": { + "message": "Преузети Bitwarden" + }, + "downloadBitwardenOnAllDevices": { + "message": "Преузети Bitwarden на све уређаје" + }, + "getTheMobileApp": { + "message": "Узети мобилну апликацију" + }, + "getTheMobileAppDesc": { + "message": "Приступите лозинци у покрету са Bitwarden мобилном апликацијом." + }, + "getTheDesktopApp": { + "message": "Узети desktop апликацију" + }, + "getTheDesktopAppDesc": { + "message": "Приступите свом сефу без прегледача, а затим поставите откључавање са биометристима да бисте убрзали откључавање и у програму и у додатку." + }, + "downloadFromBitwardenNow": { + "message": "Преузети сада са bitwarden.com" + }, + "getItOnGooglePlay": { + "message": "Набавите на Google Play" + }, + "downloadOnTheAppStore": { + "message": "Преузмите са App Store" + }, "permanentlyDeleteAttachmentConfirmation": { "message": "Да ли сте сигурни да желите да трајно избришете овај прилог?" }, @@ -4917,6 +5026,15 @@ "biometricsStatusHelptextUnavailableReasonUnknown": { "message": "Биометријско откључавање није доступно из непознатог разлога." }, + "unlockVault": { + "message": "Откључајте сеф у секунди" + }, + "unlockVaultDesc": { + "message": "Можете да прилагодите своје поставке за откључавање и истек времена да бисте брзо приступили сефу." + }, + "unlockPinSet": { + "message": "Постављен ПИН деблокирања" + }, "authenticating": { "message": "Аутентификација" }, @@ -4928,8 +5046,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": { @@ -5079,42 +5197,6 @@ "beta": { "message": "Бета" }, - "importantNotice": { - "message": "Важно обавештење" - }, - "setupTwoStepLogin": { - "message": "Поставити дво-степенску пријаву" - }, - "newDeviceVerificationNoticeContentPage1": { - "message": "Bitwarden ће послати кôд на имејл вашег налога за верификовање пријављивања са нових уређаја почевши од фебруара 2025." - }, - "newDeviceVerificationNoticeContentPage2": { - "message": "Можете да подесите пријаву у два корака као алтернативни начин да заштитите свој налог или да промените свој имејл у један који можете да приступите." - }, - "remindMeLater": { - "message": "Подсети ме касније" - }, - "newDeviceVerificationNoticePageOneFormContent": { - "message": "Да ли имате поуздан приступ својим имејлом, $EMAIL$?", - "placeholders": { - "email": { - "content": "$1", - "example": "your_name@email.com" - } - } - }, - "newDeviceVerificationNoticePageOneEmailAccessNo": { - "message": "Не, ненам" - }, - "newDeviceVerificationNoticePageOneEmailAccessYes": { - "message": "Да, могу поуздано да приступим овим имејлом" - }, - "turnOnTwoStepLogin": { - "message": "Упалити дво-степенску пријаву" - }, - "changeAcctEmail": { - "message": "Променити имејл налога" - }, "extensionWidth": { "message": "Ширина додатка" }, @@ -5169,31 +5251,130 @@ "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 мобилних апликација, претраживача и десктоп апликација." + }, + "nudgeBadgeAria": { + "message": "1 нотификација" + }, + "emptyVaultNudgeTitle": { + "message": "Увоз постојеће лозинке" + }, + "emptyVaultNudgeBody": { + "message": "Користите увозник да брзо преносите пријаве у Bitwarden без да их ручно додате." + }, + "emptyVaultNudgeButton": { + "message": "Увези сада" + }, + "hasItemsVaultNudgeTitle": { + "message": "Добродошли у ваш сеф!" + }, + "hasItemsVaultNudgeBodyOne": { + "message": "Ауто-пуњење предмета за тренутну страницу" + }, + "hasItemsVaultNudgeBodyTwo": { + "message": "Предмети као омиљен за лак приступ" + }, + "hasItemsVaultNudgeBodyThree": { + "message": "Претражите сеф за нешто друго" + }, + "newLoginNudgeTitle": { + "message": "Уштедите време са ауто-пуњењем" + }, + "newLoginNudgeBodyOne": { + "message": "Укључите", + "description": "This is in multiple parts to allow for bold text in the middle of the sentence.", + "example": "Include a Website so this login appears as an autofill suggestion." + }, + "newLoginNudgeBodyBold": { + "message": "Веб сајт", + "description": "This is in multiple parts to allow for bold text in the middle of the sentence.", + "example": "Include a Website so this login appears as an autofill suggestion." + }, + "newLoginNudgeBodyTwo": { + "message": "тако да се ова пријава појављује као предлог за ауто-пуњење.", + "description": "This is in multiple parts to allow for bold text in the middle of the sentence.", + "example": "Include a Website so this login appears as an autofill suggestion." + }, + "newCardNudgeTitle": { + "message": "Seamless online checkout" + }, + "newCardNudgeBody": { + "message": "Са картицама, лако и сигурносно попуните формуларе за плаћање." + }, + "newIdentityNudgeTitle": { + "message": "Поједноставите креирање налога" + }, + "newIdentityNudgeBody": { + "message": "Са идентитетима, брзо попуните регистрације или контактних образаци." + }, + "newNoteNudgeTitle": { + "message": "Држите на сигурном своје осетљиве податке" + }, + "newNoteNudgeBody": { + "message": "Са белешкама, сигурно чувајте осетљиве податке попут банкарског или подаци о осигурању." + }, + "newSshNudgeTitle": { + "message": "Лак SSH приступ" + }, + "newSshNudgeBodyOne": { + "message": "Чувајте кључеве и повежите се са SSH агент за брзу, шифровану аутентификацију.", + "description": "Two part message", + "example": "Store your keys and connect with the SSH agent for fast, encrypted authentication. Learn more about SSH agent" + }, + "newSshNudgeBodyTwo": { + "message": "Сазнајте више о SSH агенту", + "description": "Two part message", + "example": "Store your keys and connect with the SSH agent for fast, encrypted authentication. Learn more about SSH agent" + }, + "generatorNudgeTitle": { + "message": "Брзо креирајте лозинке" + }, + "generatorNudgeBodyOne": { + "message": "Лако креирајте снажне и јединствене лозинке кликом на", + "description": "Two part message", + "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." + }, + "generatorNudgeBodyTwo": { + "message": "да вам помогне да задржите своје пријаве сигурно.", + "description": "Two part message", + "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." + }, + "generatorNudgeBodyAria": { + "message": "Лако креирајте снажне и јединствене лозинке кликом на дугме „Генерирате лозинку“ да вам помогне да чувате своје пријаве на сигурно.", + "description": "Aria label for the body content of the generator nudge" + }, + "noPermissionsViewPage": { + "message": "Немате дозволе за преглед ове странице. Покушајте да се пријавите са другим налогом." } } diff --git a/apps/browser/src/_locales/sv/messages.json b/apps/browser/src/_locales/sv/messages.json index 54a34e15013..a6cc305469d 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)" @@ -243,7 +246,7 @@ "message": "Logga in i ditt valv" }, "autoFillInfo": { - "message": "Det finns inga inloggningar tillgängliga för automatisk ifyllnad på den nuvarande fliken." + "message": "Det finns inga inloggningar tillgängliga för autofyll på den nuvarande fliken." }, "addLogin": { "message": "Lägg till en inloggning" @@ -380,7 +383,7 @@ "message": "Redigera mapp" }, "editFolderWithName": { - "message": "Edit folder: $FOLDERNAME$", + "message": "Redigera mapp: $FOLDERNAME$", "placeholders": { "foldername": { "content": "$1", @@ -572,13 +575,13 @@ "message": "Favorit" }, "unfavorite": { - "message": "Unfavorite" + "message": "Ta bort favorit" }, "itemAddedToFavorites": { - "message": "Item added to favorites" + "message": "Objekt tillagt i favoriter" }, "itemRemovedFromFavorites": { - "message": "Item removed from favorites" + "message": "Objekt borttaget från favoriter" }, "notes": { "message": "Anteckningar" @@ -605,10 +608,10 @@ "message": "Öppna" }, "launchWebsite": { - "message": "Launch website" + "message": "Öppna webbplats" }, "launchWebsiteName": { - "message": "Launch website $ITEMNAME$", + "message": "Öppna webbplats $ITEMNAME$", "placeholders": { "itemname": { "content": "$1", @@ -638,10 +641,10 @@ "message": "Ställ in en upplåsningsmetod i Inställningar" }, "sessionTimeoutHeader": { - "message": "Session timeout" + "message": "Timeout för session" }, "vaultTimeoutHeader": { - "message": "Vault timeout" + "message": "Timeout för valv" }, "otherOptions": { "message": "Andra alternativ" @@ -653,13 +656,13 @@ "message": "Din webbläsare har inte stöd för att enkelt kopiera till urklipp. Kopiera till urklipp manuellt istället." }, "verifyYourIdentity": { - "message": "Verify your identity" + "message": "Verifiera din identitet" }, "weDontRecognizeThisDevice": { "message": "We don't recognize this device. Enter the code sent to your email to verify your identity." }, "continueLoggingIn": { - "message": "Continue logging in" + "message": "Fortsätt logga in" }, "yourVaultIsLocked": { "message": "Ditt valv är låst. Verifiera din identitet för att fortsätta." @@ -839,7 +842,7 @@ "message": "Scan authenticator QR code from current webpage" }, "totpHelperTitle": { - "message": "Make 2-step verification seamless" + "message": "Gör tvåstegsverifiering sömlös" }, "totpHelper": { "message": "Bitwarden can store and fill 2-step verification codes. Copy and paste the key into this field." @@ -869,13 +872,13 @@ "message": "Logga in på Bitwarden" }, "enterTheCodeSentToYourEmail": { - "message": "Enter the code sent to your email" + "message": "Ange koden som skickats till din e-post" }, "enterTheCodeFromYourAuthenticatorApp": { - "message": "Enter the code from your authenticator app" + "message": "Ange koden från din autentiseringsapp" }, "pressYourYubiKeyToAuthenticate": { - "message": "Press your YubiKey to authenticate" + "message": "Tryck på din YubiKey för att autentisera" }, "duoTwoFactorRequiredPageSubtitle": { "message": "Duo two-step login is required for your account. Follow the steps below to finish logging in." @@ -883,17 +886,20 @@ "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" + "message": "Starta om registrering" }, "expiredLink": { - "message": "Expired link" + "message": "Utgången länk" }, "pleaseRestartRegistrationOrTryLoggingIn": { - "message": "Please restart registration or try logging in." + "message": "Starta om registreringen eller försök logga in." }, "youMayAlreadyHaveAnAccount": { - "message": "You may already have an account" + "message": "Du kanske redan har ett konto" }, "logOutConfirmation": { "message": "Är du säker på att du vill logga ut?" @@ -905,7 +911,7 @@ "message": "Nej" }, "location": { - "message": "Location" + "message": "Plats" }, "unexpectedError": { "message": "Ett okänt fel har inträffat." @@ -920,10 +926,10 @@ "message": "Tvåstegsverifiering gör ditt konto säkrare genom att kräva att du verifierar din inloggning med en annan enhet, t.ex. en säkerhetsnyckel, autentiseringsapp, SMS, telefonsamtal eller e-post. Tvåstegsverifiering kan aktiveras i Bitwardens webbvalv. Vill du besöka webbplatsen nu?" }, "twoStepLoginConfirmationContent": { - "message": "Make your account more secure by setting up two-step login in the Bitwarden web app." + "message": "Gör ditt konto säkrare genom att konfigurera tvåstegsverifiering i Bitwardens webbapp." }, "twoStepLoginConfirmationTitle": { - "message": "Continue to web app?" + "message": "Fortsätt till webbapp?" }, "editedFolder": { "message": "Mapp sparad" @@ -1025,7 +1031,7 @@ "message": "Visa kort på fliksida" }, "showCardsCurrentTabDesc": { - "message": "Lista kortobjekt på fliksidan för enkel automatisk fyllning." + "message": "Lista kortobjekt på fliksidan för enkel autofyll." }, "showIdentitiesInVaultViewV2": { "message": "Always show identities as Autofill suggestions on Vault view" @@ -1034,7 +1040,7 @@ "message": "Visa identiteter på fliksidan" }, "showIdentitiesCurrentTabDesc": { - "message": "Lista identitetsobjekt på fliksidan för enkel automatisk fyllning." + "message": "Lista identitetsobjekt på fliksidan för enkel autofyll." }, "clickToAutofillOnVault": { "message": "Click items to autofill on Vault view" @@ -1056,23 +1062,55 @@ "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.", + "notificationNewItemAria": { + "message": "New Item, opens in new window", + "description": "Aria label for the new item button in notification bar confirmation message when error is prompted" + }, + "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": { - "content": "$1" + "label": { + "content": "$1", + "example": "Login" } - }, - "description": "Shown to user after login is updated." + } + }, + "notificationLoginSaveConfirmation": { + "message": "saved to Bitwarden.", + "description": "Shown to user after item is saved." + }, + "notificationLoginUpdatedConfirmation": { + "message": "updated in Bitwarden.", + "description": "Shown to user after item is updated." + }, + "selectItemAriaLabel": { + "message": "Select $ITEMTYPE$, $ITEMNAME$", + "description": "Used by screen readers. $1 is the item type (like vault or folder), $2 is the selected item name.", + "placeholders": { + "itemType": { + "content": "$1" + }, + "itemName": { + "content": "$2" + } + } }, "saveAsNewLoginAction": { "message": "Save as new login", @@ -1082,20 +1120,24 @@ "message": "Update login", "description": "Button text for updating an existing login entry." }, - "saveLoginPrompt": { - "message": "Save login?", + "unlockToSave": { + "message": "Unlock to save this login", + "description": "User prompt to take action in order to save the login they just entered." + }, + "saveLogin": { + "message": "Spara inloggning", "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": "Inloggning sparad", "description": "Message displayed when login details are successfully saved." }, "loginUpdateSuccess": { - "message": "Login updated", + "message": "Inloggning uppdaterad", "description": "Message displayed when login details are successfully updated." }, "loginUpdateTaskSuccess": { @@ -1153,7 +1195,7 @@ "message": "Uppdatera" }, "notificationUnlockDesc": { - "message": "Lås upp ditt Bitwarden-valv för att slutföra begäran om automatisk ifyllnad." + "message": "Lås upp ditt Bitwarden-valv för att slutföra begäran om autofyll." }, "notificationUnlock": { "message": "Lås upp" @@ -1175,7 +1217,7 @@ "description": "Default URI match detection for autofill." }, "defaultUriMatchDetectionDesc": { - "message": "Välj standardalternativet för hur matchning av URI är hanterat för inloggningar när du utför operationer såsom automatisk ifyllnad." + "message": "Välj standardalternativet för hur matchning av URI är hanterat för inloggningar när du utför operationer såsom autofyll." }, "theme": { "message": "Tema" @@ -1232,7 +1274,7 @@ "description": "WARNING (should stay in capitalized letters if the language permits)" }, "warningCapitalized": { - "message": "Warning", + "message": "Varning", "description": "Warning (should maintain locale-relevant capitalization)" }, "confirmVaultExport": { @@ -1323,8 +1365,8 @@ "featureUnavailable": { "message": "Funktion ej tillgänglig" }, - "encryptionKeyMigrationRequired": { - "message": "Migrering av krypteringsnyckel krävs. Logga in på webbvalvet för att uppdatera din krypteringsnyckel." + "legacyEncryptionUnsupported": { + "message": "Legacy encryption is no longer supported. Please contact support to recover your account." }, "premiumMembership": { "message": "Premium-medlemskap" @@ -1432,29 +1474,29 @@ } }, "dontAskAgainOnThisDeviceFor30Days": { - "message": "Don't ask again on this device for 30 days" + "message": "Fråga inte igen på den här enheten i 30 dagar" }, "selectAnotherMethod": { - "message": "Select another method", + "message": "Välj en annan metod", "description": "Select another two-step login method" }, "useYourRecoveryCode": { - "message": "Use your recovery code" + "message": "Använd din återställningskod" }, "insertU2f": { "message": "Sätt i din säkerhetsnyckel i en av datorns USB-portar. Om nyckeln har en knapp, sätt fingret på den." }, "openInNewTab": { - "message": "Open in new tab" + "message": "Öppna i ny flik" }, "webAuthnAuthenticate": { "message": "Autentisera WebAuthn" }, "readSecurityKey": { - "message": "Read security key" + "message": "Läs säkerhetsnyckel" }, "awaitingSecurityKeyInteraction": { - "message": "Awaiting security key interaction..." + "message": "Väntar på interaktion med säkerhetsnyckel..." }, "loginUnavailable": { "message": "Inloggning ej tillgänglig" @@ -1469,7 +1511,7 @@ "message": "Alternativ för tvåstegsverifiering" }, "selectTwoStepLoginMethod": { - "message": "Select two-step login method" + "message": "Välj metod för tvåstegsverifiering" }, "recoveryCodeDesc": { "message": "Förlorat åtkomst till alla dina metoder för tvåstegsverifiering? Använd din återställningskod för att inaktivera tvåstegsverifiering på ditt konto." @@ -1481,7 +1523,7 @@ "message": "Autentiseringsapp" }, "authenticatorAppDescV2": { - "message": "Enter a code generated by an authenticator app like Bitwarden Authenticator.", + "message": "Ange en kod som genererats av en autentiseringsapp som Bitwarden Authenticator.", "description": "'Bitwarden Authenticator' is a product name and should not be translated." }, "yubiKeyTitleV2": { @@ -1514,13 +1556,13 @@ "message": "Egen-hostad miljö" }, "selfHostedBaseUrlHint": { - "message": "Specify the base URL of your on-premises hosted Bitwarden installation. Example: https://bitwarden.company.com" + "message": "Ange bas-URL:en för din självhostade Bitwarden-installation. Exempel: https://bitwarden.company.com" }, "selfHostedCustomEnvHeader": { - "message": "For advanced configuration, you can specify the base URL of each service independently." + "message": "För avancerad konfiguration kan du ange bas-URL för varje tjänst separat." }, "selfHostedEnvFormInvalid": { - "message": "You must add either the base Server URL or at least one custom environment." + "message": "Du måste lägga till antingen serverns bas-URL eller minst en anpassad miljö." }, "customEnvironment": { "message": "Anpassad miljö" @@ -1529,7 +1571,7 @@ "message": "Server-URL" }, "selfHostBaseUrl": { - "message": "Self-host server URL", + "message": "Självhostad server-URL", "description": "Label for field requesting a self-hosted integration service URL" }, "apiUrl": { @@ -1551,12 +1593,30 @@ "message": "Miljö-URL:erna har sparats" }, "showAutoFillMenuOnFormFields": { - "message": "Visa menyn för automatisk ifyllnad på formulärfält", + "message": "Visa menyn för autofyll på formulärfält", "description": "Represents the message for allowing the user to enable the autofill overlay" }, "autofillSuggestionsSectionTitle": { "message": "Förslag för autofyll" }, + "autofillSpotlightTitle": { + "message": "Hitta förslag på autofyll enkelt" + }, + "autofillSpotlightDesc": { + "message": "Stäng av webbläsarens autofyllinställningar så att de inte orsakar konflikt med Bitwarden." + }, + "turnOffBrowserAutofill": { + "message": "Stäng av $BROWSER$ autofyll", + "placeholders": { + "browser": { + "content": "$1", + "example": "Chrome" + } + } + }, + "turnOffAutofill": { + "message": "Stäng av autofyll" + }, "showInlineMenuLabel": { "message": "Visa förslag för autofyll i formulärfält" }, @@ -1567,13 +1627,13 @@ "message": "Visa kort som förslag" }, "showInlineMenuOnIconSelectionLabel": { - "message": "Display suggestions when icon is selected" + "message": "Visa förslag när ikonen markerats" }, "showInlineMenuOnFormFieldsDescAlt": { - "message": "Applies to all logged in accounts." + "message": "Gäller för alla inloggade konton." }, "turnOffBrowserBuiltInPasswordManagerSettings": { - "message": "Turn off your browser's built in password manager settings to avoid conflicts." + "message": "Stäng av webbläsarens inbyggda lösenordshanterarinställningar för att undvika konflikter." }, "turnOffBrowserBuiltInPasswordManagerSettingsLink": { "message": "Redigera webbläsarinställningar." @@ -1583,11 +1643,11 @@ "description": "Overlay setting select option for disabling autofill overlay" }, "autofillOverlayVisibilityOnFieldFocus": { - "message": "When field is selected (on focus)", + "message": "När fältet är markerat (i fokus)", "description": "Overlay appearance select option for showing the field on focus of the input element" }, "autofillOverlayVisibilityOnButtonClick": { - "message": "När ikonen för automatisk ifyllnad är vald", + "message": "När ikonen för autofyll är vald", "description": "Overlay appearance select option for showing the field on click of the overlay icon" }, "enableAutoFillOnPageLoadSectionTitle": { @@ -1633,13 +1693,13 @@ "message": "Öppna valvet i sidofältet" }, "commandAutofillLoginDesc": { - "message": "Autofill the last used login for the current website" + "message": "Autofyll den senast använda inloggningen för den aktuella webbplatsen" }, "commandAutofillCardDesc": { - "message": "Autofill the last used card for the current website" + "message": "Autofyll det senast använda kortet för den aktuella webbplatsen" }, "commandAutofillIdentityDesc": { - "message": "Autofill the last used identity for the current website" + "message": "Autofyll den senast använda identiteten för den aktuella webbplatsen" }, "commandGeneratePasswordDesc": { "message": "Skapa och kopiera ett nytt slumpmässigt lösenord till urklipp." @@ -1663,7 +1723,7 @@ "message": "Dra för att sortera" }, "dragToReorder": { - "message": "Drag to reorder" + "message": "Dra för att ändra ordning" }, "cfTypeText": { "message": "Text" @@ -1698,7 +1758,7 @@ "message": "Visa en identifierbar bild bredvid varje inloggning." }, "faviconDescAlt": { - "message": "Show a recognizable image next to each login. Applies to all logged in accounts." + "message": "Visa en igenkännbar bild bredvid varje inloggning. Gäller för alla inloggade konton." }, "enableBadgeCounter": { "message": "Visa aktivitetsräknaren" @@ -1899,7 +1959,7 @@ "message": "Rensa generatorhistorik" }, "cleargGeneratorHistoryDescription": { - "message": "If you continue, all entries will be permanently deleted from generator's history. Are you sure you want to continue?" + "message": "Om du fortsätter kommer alla poster att raderas permanent från generatorns historik. Är du säker på att du vill fortsätta?" }, "back": { "message": "Tillbaka" @@ -1938,7 +1998,7 @@ "message": "Säkra anteckningar" }, "sshKeys": { - "message": "SSH Keys" + "message": "SSH-nycklar" }, "clear": { "message": "Rensa", @@ -2021,10 +2081,10 @@ "message": "Rensa historik" }, "nothingToShow": { - "message": "Nothing to show" + "message": "Inget tillgängligt innehåll" }, "nothingGeneratedRecently": { - "message": "You haven't generated anything recently" + "message": "Du har inte genererat något nyligen" }, "remove": { "message": "Ta bort" @@ -2094,7 +2154,7 @@ "message": "Ange en PIN-kod för att låsa upp Bitwarden. Dina PIN-inställningar återställs om du någonsin loggar ut helt från programmet." }, "setYourPinCode1": { - "message": "Your PIN will be used to unlock Bitwarden instead of your master password. Your PIN will reset if you ever fully log out of Bitwarden." + "message": "Din PIN-kod kommer att användas för att låsa upp Bitwarden istället för ditt huvudlösenord. Din PIN-kod kommer att återställas om du någonsin helt loggar ut från Bitwarden." }, "pinRequired": { "message": "PIN-kod krävs." @@ -2121,7 +2181,7 @@ "message": "Lås med huvudlösenordet vid omstart av webbläsaren" }, "lockWithMasterPassOnRestart1": { - "message": "Require master password on browser restart" + "message": "Kräv huvudlösenord vid omstart av webbläsaren" }, "selectOneCollection": { "message": "Du måste markera minst en samling." @@ -2136,45 +2196,39 @@ "message": "Lösenordsgenerator" }, "usernameGenerator": { - "message": "Username generator" + "message": "Användarnamnsgenerator" }, "useThisEmail": { - "message": "Use this email" + "message": "Använd denna e-post" }, "useThisPassword": { - "message": "Use this password" + "message": "Använd detta lösenord" + }, + "useThisPassphrase": { + "message": "Use this passphrase" }, "useThisUsername": { - "message": "Use this username" + "message": "Använd detta användarnamn" }, "securePasswordGenerated": { - "message": "Secure password generated! Don't forget to also update your password on the website." + "message": "Säkert lösenord genererat! Glöm inte att även uppdatera ditt lösenord på webbplatsen." }, "useGeneratorHelpTextPartOne": { - "message": "Use the generator", + "message": "Använd generatorn", "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", + "message": "för att skapa ett starkt unikt lösenord", "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": "Anpassning av valv" }, "vaultTimeoutAction": { "message": "Åtgärd när valvets tidsgräns överskrids" }, "vaultTimeoutAction1": { - "message": "Timeout action" - }, - "newCustomizationOptionsCalloutTitle": { - "message": "New customization options" - }, - "newCustomizationOptionsCalloutContent": { - "message": "Customize your vault experience with quick copy actions, compact mode, and more!" - }, - "newCustomizationOptionsCalloutLink": { - "message": "View all Appearance settings" + "message": "Åtgärd vid timeout" }, "lock": { "message": "Lås", @@ -2296,16 +2350,16 @@ "message": "Ditt nya huvudlösenord uppfyller inte kraven i policyn." }, "receiveMarketingEmailsV2": { - "message": "Get advice, announcements, and research opportunities from Bitwarden in your inbox." + "message": "Få råd, nyheter och forskningsmöjligheter från Bitwarden i din inkorg." }, "unsubscribe": { - "message": "Unsubscribe" + "message": "Avprenumerera" }, "atAnyTime": { "message": "när som helst." }, "byContinuingYouAgreeToThe": { - "message": "By continuing, you agree to the" + "message": "Genom att fortsätta godkänner du" }, "and": { "message": "och" @@ -2623,6 +2677,10 @@ "message": "Alla Sends", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, + "maxAccessCountReached": { + "message": "Max access count reached", + "description": "This text will be displayed after a Send has been accessed the maximum amount of times." + }, "hideTextByDefault": { "message": "Hide text by default" }, @@ -2820,7 +2878,7 @@ "message": "E-postverifiering krävs" }, "emailVerifiedV2": { - "message": "Email verified" + "message": "E-post verifierad" }, "emailVerificationRequiredDesc": { "message": "Du måste verifiera din e-postadress för att använda den här funktionen. Du kan verifiera din e-postadress i webbvalvet." @@ -2963,14 +3021,14 @@ "copyCustomFieldNameNotUnique": { "message": "Ingen unik identifierare hittades." }, - "convertOrganizationEncryptionDesc": { - "message": "$ORGANIZATION$ använder SSO med en egen nyckelserver. Ett huvudlösenord krävs inte längre för att logga in för medlemmar i denna organisation.", - "placeholders": { - "organization": { - "content": "$1", - "example": "My Org Name" - } - } + "removeMasterPasswordForOrganizationUserKeyConnector": { + "message": "A master password is no longer required for members of the following organization. Please confirm the domain below with your organization administrator." + }, + "organizationName": { + "message": "Organisationsnamn" + }, + "keyConnectorDomain": { + "message": "Key Connector domain" }, "leaveOrganization": { "message": "Lämna organisation" @@ -3532,12 +3590,36 @@ "deviceTrusted": { "message": "Enhet betrodd" }, - "sendsNoItemsTitle": { - "message": "Inga aktiva Sends", + "trustOrganization": { + "message": "Trust organization" + }, + "trust": { + "message": "Trust" + }, + "doNotTrust": { + "message": "Do not trust" + }, + "organizationNotTrusted": { + "message": "Organization is not trusted" + }, + "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" + }, + "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." + }, + "orgTrustWarning1": { + "message": "This organization has an Enterprise policy that will enroll you in account recovery. Enrollment will allow organization administrators to change your password. Only proceed if you recognize this organization and the fingerprint phrase displayed below matches the organization's fingerprint." + }, + "trustUser": { + "message": "Trust user" + }, + "sendsTitleNoItems": { + "message": "Send sensitive information safely", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, - "sendsNoItemsMessage": { - "message": "Use Send to securely share encrypted information with anyone.", + "sendsBodyNoItems": { + "message": "Share files and data securely with anyone, on any platform. Your information will remain end-to-end encrypted while limiting exposure.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "inputRequired": { @@ -3694,7 +3776,7 @@ "description": "Text to display in overlay when the account is locked." }, "unlockYourAccountToViewAutofillSuggestions": { - "message": "Unlock your account to view autofill suggestions", + "message": "Lås upp ditt konto för att visa förslag för autofyll", "description": "Text to display in overlay when the account is locked." }, "unlockAccount": { @@ -4155,7 +4237,7 @@ "description": "Dialog title facilitating the ability to override a chrome browser's default autofill behavior" }, "overrideDefaultBrowserAutofillDescription": { - "message": "Ignoring this option may cause conflicts between Bitwarden autofill suggestions and your browser's.", + "message": "Att ignorera det här alternativet kan orsaka konflikter mellan Bitwardens autofyllförslag och webbläsarens.", "description": "Dialog message facilitating the ability to override a chrome browser's default autofill behavior" }, "overrideDefaultBrowserAutoFillSettings": { @@ -4204,13 +4286,13 @@ "message": "Passkey borttagen" }, "autofillSuggestions": { - "message": "Autofill suggestions" + "message": "Förslag för autofyll" }, "itemSuggestions": { "message": "Suggested items" }, "autofillSuggestionsTip": { - "message": "Save a login item for this site to autofill" + "message": "Spara ett inloggningsobjekt för den här webbplatsen för autofyll" }, "yourVaultIsEmpty": { "message": "Ditt valv är tomt" @@ -4456,6 +4538,33 @@ } } }, + "downloadBitwarden": { + "message": "Download Bitwarden" + }, + "downloadBitwardenOnAllDevices": { + "message": "Download Bitwarden on all devices" + }, + "getTheMobileApp": { + "message": "Get the mobile app" + }, + "getTheMobileAppDesc": { + "message": "Access your passwords on the go with the Bitwarden mobile app." + }, + "getTheDesktopApp": { + "message": "Get the desktop app" + }, + "getTheDesktopAppDesc": { + "message": "Access your vault without a browser, then set up unlock with biometrics to expedite unlocking in both the desktop app and browser extension." + }, + "downloadFromBitwardenNow": { + "message": "Download from bitwarden.com now" + }, + "getItOnGooglePlay": { + "message": "Get it on Google Play" + }, + "downloadOnTheAppStore": { + "message": "Download on the App Store" + }, "permanentlyDeleteAttachmentConfirmation": { "message": "Are you sure you want to permanently delete this attachment?" }, @@ -4816,7 +4925,7 @@ "message": "Kontoåtgärder" }, "showNumberOfAutofillSuggestions": { - "message": "Show number of login autofill suggestions on extension icon" + "message": "Visa antal autofyllförslag för inloggning på tilläggsikonen" }, "showQuickCopyActions": { "message": "Show quick copy actions on Vault" @@ -4917,6 +5026,15 @@ "biometricsStatusHelptextUnavailableReasonUnknown": { "message": "Biometric unlock is currently unavailable for an unknown reason." }, + "unlockVault": { + "message": "Unlock your vault in seconds" + }, + "unlockVaultDesc": { + "message": "You can customize your unlock and timeout settings to more quickly access your vault." + }, + "unlockPinSet": { + "message": "Unlock PIN set" + }, "authenticating": { "message": "Authenticating" }, @@ -4928,8 +5046,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": { @@ -5079,42 +5197,6 @@ "beta": { "message": "Beta" }, - "importantNotice": { - "message": "Important notice" - }, - "setupTwoStepLogin": { - "message": "Set up two-step login" - }, - "newDeviceVerificationNoticeContentPage1": { - "message": "Bitwarden will send a code to your account email to verify logins from new devices starting in February 2025." - }, - "newDeviceVerificationNoticeContentPage2": { - "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" - }, - "newDeviceVerificationNoticePageOneFormContent": { - "message": "Do you have reliable access to your email, $EMAIL$?", - "placeholders": { - "email": { - "content": "$1", - "example": "your_name@email.com" - } - } - }, - "newDeviceVerificationNoticePageOneEmailAccessNo": { - "message": "No, I do not" - }, - "newDeviceVerificationNoticePageOneEmailAccessYes": { - "message": "Yes, I can reliably access my email" - }, - "turnOnTwoStepLogin": { - "message": "Turn on two-step login" - }, - "changeAcctEmail": { - "message": "Change account email" - }, "extensionWidth": { "message": "Extension width" }, @@ -5169,6 +5251,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 +5283,98 @@ }, "secureDevicesBody": { "message": "Save unlimited passwords across unlimited devices with Bitwarden mobile, browser, and desktop apps." + }, + "nudgeBadgeAria": { + "message": "1 notification" + }, + "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!" + }, + "hasItemsVaultNudgeBodyOne": { + "message": "Autofill items for the current page" + }, + "hasItemsVaultNudgeBodyTwo": { + "message": "Favorite items for easy access" + }, + "hasItemsVaultNudgeBodyThree": { + "message": "Search your vault for something else" + }, + "newLoginNudgeTitle": { + "message": "Save time with autofill" + }, + "newLoginNudgeBodyOne": { + "message": "Include a", + "description": "This is in multiple parts to allow for bold text in the middle of the sentence.", + "example": "Include a Website so this login appears as an autofill suggestion." + }, + "newLoginNudgeBodyBold": { + "message": "Website", + "description": "This is in multiple parts to allow for bold text in the middle of the sentence.", + "example": "Include a Website so this login appears as an autofill suggestion." + }, + "newLoginNudgeBodyTwo": { + "message": "så att den här inloggningen visas som ett förslag för autofyll.", + "description": "This is in multiple parts to allow for bold text in the middle of the sentence.", + "example": "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" + }, + "newSshNudgeBodyOne": { + "message": "Store your keys and connect with the SSH agent for fast, encrypted authentication.", + "description": "Two part message", + "example": "Store your keys and connect with the SSH agent for fast, encrypted authentication. Learn more about SSH agent" + }, + "newSshNudgeBodyTwo": { + "message": "Learn more about SSH agent", + "description": "Two part message", + "example": "Store your keys and connect with the SSH agent for fast, encrypted authentication. Learn more about SSH agent" + }, + "generatorNudgeTitle": { + "message": "Quickly create passwords" + }, + "generatorNudgeBodyOne": { + "message": "Easily create strong and unique passwords by clicking on", + "description": "Two part message", + "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." + }, + "generatorNudgeBodyTwo": { + "message": "to help you keep your logins secure.", + "description": "Two part message", + "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." + }, + "generatorNudgeBodyAria": { + "message": "Easily create strong and unique passwords by clicking on the Generate password button to help you keep your logins secure.", + "description": "Aria label for the body content of the generator nudge" + }, + "noPermissionsViewPage": { + "message": "You do not have permissions to view this page. Try logging in with a different account." } } diff --git a/apps/browser/src/_locales/te/messages.json b/apps/browser/src/_locales/te/messages.json index 7bec167e1c6..032d8c89d49 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,55 @@ "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.", + "notificationNewItemAria": { + "message": "New Item, opens in new window", + "description": "Aria label for the new item button in notification bar confirmation message when error is prompted" + }, + "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": { - "content": "$1" + "label": { + "content": "$1", + "example": "Login" } - }, - "description": "Shown to user after login is updated." + } + }, + "notificationLoginSaveConfirmation": { + "message": "saved to Bitwarden.", + "description": "Shown to user after item is saved." + }, + "notificationLoginUpdatedConfirmation": { + "message": "updated in Bitwarden.", + "description": "Shown to user after item is updated." + }, + "selectItemAriaLabel": { + "message": "Select $ITEMTYPE$, $ITEMNAME$", + "description": "Used by screen readers. $1 is the item type (like vault or folder), $2 is the selected item name.", + "placeholders": { + "itemType": { + "content": "$1" + }, + "itemName": { + "content": "$2" + } + } }, "saveAsNewLoginAction": { "message": "Save as new login", @@ -1082,12 +1120,16 @@ "message": "Update login", "description": "Button text for updating an existing login entry." }, - "saveLoginPrompt": { - "message": "Save login?", + "unlockToSave": { + "message": "Unlock to save this login", + "description": "User prompt to take action in order to save the login they just entered." + }, + "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": { @@ -1323,8 +1365,8 @@ "featureUnavailable": { "message": "Feature unavailable" }, - "encryptionKeyMigrationRequired": { - "message": "Encryption key migration required. Please login through the web vault to update your encryption key." + "legacyEncryptionUnsupported": { + "message": "Legacy encryption is no longer supported. Please contact support to recover your account." }, "premiumMembership": { "message": "Premium membership" @@ -1557,6 +1599,24 @@ "autofillSuggestionsSectionTitle": { "message": "Autofill suggestions" }, + "autofillSpotlightTitle": { + "message": "Easily find autofill suggestions" + }, + "autofillSpotlightDesc": { + "message": "Turn off your browser's autofill settings, so they don't conflict with Bitwarden." + }, + "turnOffBrowserAutofill": { + "message": "Turn off $BROWSER$ autofill", + "placeholders": { + "browser": { + "content": "$1", + "example": "Chrome" + } + } + }, + "turnOffAutofill": { + "message": "Turn off autofill" + }, "showInlineMenuLabel": { "message": "Show autofill suggestions on form fields" }, @@ -2144,6 +2204,9 @@ "useThisPassword": { "message": "Use this password" }, + "useThisPassphrase": { + "message": "Use this passphrase" + }, "useThisUsername": { "message": "Use this username" }, @@ -2167,15 +2230,6 @@ "vaultTimeoutAction1": { "message": "Timeout action" }, - "newCustomizationOptionsCalloutTitle": { - "message": "New customization options" - }, - "newCustomizationOptionsCalloutContent": { - "message": "Customize your vault experience with quick copy actions, compact mode, and more!" - }, - "newCustomizationOptionsCalloutLink": { - "message": "View all Appearance settings" - }, "lock": { "message": "Lock", "description": "Verb form: to make secure or inaccessible by" @@ -2623,6 +2677,10 @@ "message": "All Sends", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, + "maxAccessCountReached": { + "message": "Max access count reached", + "description": "This text will be displayed after a Send has been accessed the maximum amount of times." + }, "hideTextByDefault": { "message": "Hide text by default" }, @@ -2963,14 +3021,14 @@ "copyCustomFieldNameNotUnique": { "message": "No unique identifier found." }, - "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.", - "placeholders": { - "organization": { - "content": "$1", - "example": "My Org Name" - } - } + "removeMasterPasswordForOrganizationUserKeyConnector": { + "message": "A master password is no longer required for members of the following organization. Please confirm the domain below with your organization administrator." + }, + "organizationName": { + "message": "Organization name" + }, + "keyConnectorDomain": { + "message": "Key Connector domain" }, "leaveOrganization": { "message": "Leave organization" @@ -3532,12 +3590,36 @@ "deviceTrusted": { "message": "Device trusted" }, - "sendsNoItemsTitle": { - "message": "No active Sends", + "trustOrganization": { + "message": "Trust organization" + }, + "trust": { + "message": "Trust" + }, + "doNotTrust": { + "message": "Do not trust" + }, + "organizationNotTrusted": { + "message": "Organization is not trusted" + }, + "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" + }, + "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." + }, + "orgTrustWarning1": { + "message": "This organization has an Enterprise policy that will enroll you in account recovery. Enrollment will allow organization administrators to change your password. Only proceed if you recognize this organization and the fingerprint phrase displayed below matches the organization's fingerprint." + }, + "trustUser": { + "message": "Trust user" + }, + "sendsTitleNoItems": { + "message": "Send sensitive information safely", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, - "sendsNoItemsMessage": { - "message": "Use Send to securely share encrypted information with anyone.", + "sendsBodyNoItems": { + "message": "Share files and data securely with anyone, on any platform. Your information will remain end-to-end encrypted while limiting exposure.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "inputRequired": { @@ -4456,6 +4538,33 @@ } } }, + "downloadBitwarden": { + "message": "Download Bitwarden" + }, + "downloadBitwardenOnAllDevices": { + "message": "Download Bitwarden on all devices" + }, + "getTheMobileApp": { + "message": "Get the mobile app" + }, + "getTheMobileAppDesc": { + "message": "Access your passwords on the go with the Bitwarden mobile app." + }, + "getTheDesktopApp": { + "message": "Get the desktop app" + }, + "getTheDesktopAppDesc": { + "message": "Access your vault without a browser, then set up unlock with biometrics to expedite unlocking in both the desktop app and browser extension." + }, + "downloadFromBitwardenNow": { + "message": "Download from bitwarden.com now" + }, + "getItOnGooglePlay": { + "message": "Get it on Google Play" + }, + "downloadOnTheAppStore": { + "message": "Download on the App Store" + }, "permanentlyDeleteAttachmentConfirmation": { "message": "Are you sure you want to permanently delete this attachment?" }, @@ -4917,6 +5026,15 @@ "biometricsStatusHelptextUnavailableReasonUnknown": { "message": "Biometric unlock is currently unavailable for an unknown reason." }, + "unlockVault": { + "message": "Unlock your vault in seconds" + }, + "unlockVaultDesc": { + "message": "You can customize your unlock and timeout settings to more quickly access your vault." + }, + "unlockPinSet": { + "message": "Unlock PIN set" + }, "authenticating": { "message": "Authenticating" }, @@ -4928,8 +5046,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": { @@ -5079,42 +5197,6 @@ "beta": { "message": "Beta" }, - "importantNotice": { - "message": "Important notice" - }, - "setupTwoStepLogin": { - "message": "Set up two-step login" - }, - "newDeviceVerificationNoticeContentPage1": { - "message": "Bitwarden will send a code to your account email to verify logins from new devices starting in February 2025." - }, - "newDeviceVerificationNoticeContentPage2": { - "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" - }, - "newDeviceVerificationNoticePageOneFormContent": { - "message": "Do you have reliable access to your email, $EMAIL$?", - "placeholders": { - "email": { - "content": "$1", - "example": "your_name@email.com" - } - } - }, - "newDeviceVerificationNoticePageOneEmailAccessNo": { - "message": "No, I do not" - }, - "newDeviceVerificationNoticePageOneEmailAccessYes": { - "message": "Yes, I can reliably access my email" - }, - "turnOnTwoStepLogin": { - "message": "Turn on two-step login" - }, - "changeAcctEmail": { - "message": "Change account email" - }, "extensionWidth": { "message": "Extension width" }, @@ -5169,6 +5251,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 +5283,98 @@ }, "secureDevicesBody": { "message": "Save unlimited passwords across unlimited devices with Bitwarden mobile, browser, and desktop apps." + }, + "nudgeBadgeAria": { + "message": "1 notification" + }, + "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!" + }, + "hasItemsVaultNudgeBodyOne": { + "message": "Autofill items for the current page" + }, + "hasItemsVaultNudgeBodyTwo": { + "message": "Favorite items for easy access" + }, + "hasItemsVaultNudgeBodyThree": { + "message": "Search your vault for something else" + }, + "newLoginNudgeTitle": { + "message": "Save time with autofill" + }, + "newLoginNudgeBodyOne": { + "message": "Include a", + "description": "This is in multiple parts to allow for bold text in the middle of the sentence.", + "example": "Include a Website so this login appears as an autofill suggestion." + }, + "newLoginNudgeBodyBold": { + "message": "Website", + "description": "This is in multiple parts to allow for bold text in the middle of the sentence.", + "example": "Include a Website so this login appears as an autofill suggestion." + }, + "newLoginNudgeBodyTwo": { + "message": "so this login appears as an autofill suggestion.", + "description": "This is in multiple parts to allow for bold text in the middle of the sentence.", + "example": "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" + }, + "newSshNudgeBodyOne": { + "message": "Store your keys and connect with the SSH agent for fast, encrypted authentication.", + "description": "Two part message", + "example": "Store your keys and connect with the SSH agent for fast, encrypted authentication. Learn more about SSH agent" + }, + "newSshNudgeBodyTwo": { + "message": "Learn more about SSH agent", + "description": "Two part message", + "example": "Store your keys and connect with the SSH agent for fast, encrypted authentication. Learn more about SSH agent" + }, + "generatorNudgeTitle": { + "message": "Quickly create passwords" + }, + "generatorNudgeBodyOne": { + "message": "Easily create strong and unique passwords by clicking on", + "description": "Two part message", + "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." + }, + "generatorNudgeBodyTwo": { + "message": "to help you keep your logins secure.", + "description": "Two part message", + "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." + }, + "generatorNudgeBodyAria": { + "message": "Easily create strong and unique passwords by clicking on the Generate password button to help you keep your logins secure.", + "description": "Aria label for the body content of the generator nudge" + }, + "noPermissionsViewPage": { + "message": "You do not have permissions to view this page. Try logging in with a different account." } } diff --git a/apps/browser/src/_locales/th/messages.json b/apps/browser/src/_locales/th/messages.json index b502e29cfdd..46fe36ab0da 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" + }, "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,55 @@ "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.", + "notificationNewItemAria": { + "message": "New Item, opens in new window", + "description": "Aria label for the new item button in notification bar confirmation message when error is prompted" + }, + "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": { - "content": "$1" + "label": { + "content": "$1", + "example": "Login" } - }, - "description": "Shown to user after login is updated." + } + }, + "notificationLoginSaveConfirmation": { + "message": "saved to Bitwarden.", + "description": "Shown to user after item is saved." + }, + "notificationLoginUpdatedConfirmation": { + "message": "updated in Bitwarden.", + "description": "Shown to user after item is updated." + }, + "selectItemAriaLabel": { + "message": "Select $ITEMTYPE$, $ITEMNAME$", + "description": "Used by screen readers. $1 is the item type (like vault or folder), $2 is the selected item name.", + "placeholders": { + "itemType": { + "content": "$1" + }, + "itemName": { + "content": "$2" + } + } }, "saveAsNewLoginAction": { "message": "Save as new login", @@ -1082,12 +1120,16 @@ "message": "Update login", "description": "Button text for updating an existing login entry." }, - "saveLoginPrompt": { - "message": "Save login?", + "unlockToSave": { + "message": "Unlock to save this login", + "description": "User prompt to take action in order to save the login they just entered." + }, + "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": { @@ -1323,8 +1365,8 @@ "featureUnavailable": { "message": "Feature Unavailable" }, - "encryptionKeyMigrationRequired": { - "message": "Encryption key migration required. Please login through the web vault to update your encryption key." + "legacyEncryptionUnsupported": { + "message": "Legacy encryption is no longer supported. Please contact support to recover your account." }, "premiumMembership": { "message": "Premium Membership" @@ -1557,6 +1599,24 @@ "autofillSuggestionsSectionTitle": { "message": "Autofill suggestions" }, + "autofillSpotlightTitle": { + "message": "Easily find autofill suggestions" + }, + "autofillSpotlightDesc": { + "message": "Turn off your browser's autofill settings, so they don't conflict with Bitwarden." + }, + "turnOffBrowserAutofill": { + "message": "Turn off $BROWSER$ autofill", + "placeholders": { + "browser": { + "content": "$1", + "example": "Chrome" + } + } + }, + "turnOffAutofill": { + "message": "Turn off autofill" + }, "showInlineMenuLabel": { "message": "Show autofill suggestions on form fields" }, @@ -2144,6 +2204,9 @@ "useThisPassword": { "message": "Use this password" }, + "useThisPassphrase": { + "message": "Use this passphrase" + }, "useThisUsername": { "message": "Use this username" }, @@ -2167,15 +2230,6 @@ "vaultTimeoutAction1": { "message": "Timeout action" }, - "newCustomizationOptionsCalloutTitle": { - "message": "New customization options" - }, - "newCustomizationOptionsCalloutContent": { - "message": "Customize your vault experience with quick copy actions, compact mode, and more!" - }, - "newCustomizationOptionsCalloutLink": { - "message": "View all Appearance settings" - }, "lock": { "message": "ล็อก", "description": "Verb form: to make secure or inaccessible by" @@ -2623,6 +2677,10 @@ "message": "Send ทั้งหมด", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, + "maxAccessCountReached": { + "message": "Max access count reached", + "description": "This text will be displayed after a Send has been accessed the maximum amount of times." + }, "hideTextByDefault": { "message": "Hide text by default" }, @@ -2963,14 +3021,14 @@ "copyCustomFieldNameNotUnique": { "message": "No unique identifier found." }, - "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.", - "placeholders": { - "organization": { - "content": "$1", - "example": "My Org Name" - } - } + "removeMasterPasswordForOrganizationUserKeyConnector": { + "message": "A master password is no longer required for members of the following organization. Please confirm the domain below with your organization administrator." + }, + "organizationName": { + "message": "Organization name" + }, + "keyConnectorDomain": { + "message": "Key Connector domain" }, "leaveOrganization": { "message": "Leave organization" @@ -3532,12 +3590,36 @@ "deviceTrusted": { "message": "Device trusted" }, - "sendsNoItemsTitle": { - "message": "No active Sends", + "trustOrganization": { + "message": "Trust organization" + }, + "trust": { + "message": "Trust" + }, + "doNotTrust": { + "message": "Do not trust" + }, + "organizationNotTrusted": { + "message": "Organization is not trusted" + }, + "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" + }, + "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." + }, + "orgTrustWarning1": { + "message": "This organization has an Enterprise policy that will enroll you in account recovery. Enrollment will allow organization administrators to change your password. Only proceed if you recognize this organization and the fingerprint phrase displayed below matches the organization's fingerprint." + }, + "trustUser": { + "message": "Trust user" + }, + "sendsTitleNoItems": { + "message": "Send sensitive information safely", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, - "sendsNoItemsMessage": { - "message": "Use Send to securely share encrypted information with anyone.", + "sendsBodyNoItems": { + "message": "Share files and data securely with anyone, on any platform. Your information will remain end-to-end encrypted while limiting exposure.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "inputRequired": { @@ -4456,6 +4538,33 @@ } } }, + "downloadBitwarden": { + "message": "Download Bitwarden" + }, + "downloadBitwardenOnAllDevices": { + "message": "Download Bitwarden on all devices" + }, + "getTheMobileApp": { + "message": "Get the mobile app" + }, + "getTheMobileAppDesc": { + "message": "Access your passwords on the go with the Bitwarden mobile app." + }, + "getTheDesktopApp": { + "message": "Get the desktop app" + }, + "getTheDesktopAppDesc": { + "message": "Access your vault without a browser, then set up unlock with biometrics to expedite unlocking in both the desktop app and browser extension." + }, + "downloadFromBitwardenNow": { + "message": "Download from bitwarden.com now" + }, + "getItOnGooglePlay": { + "message": "Get it on Google Play" + }, + "downloadOnTheAppStore": { + "message": "Download on the App Store" + }, "permanentlyDeleteAttachmentConfirmation": { "message": "Are you sure you want to permanently delete this attachment?" }, @@ -4917,6 +5026,15 @@ "biometricsStatusHelptextUnavailableReasonUnknown": { "message": "Biometric unlock is currently unavailable for an unknown reason." }, + "unlockVault": { + "message": "Unlock your vault in seconds" + }, + "unlockVaultDesc": { + "message": "You can customize your unlock and timeout settings to more quickly access your vault." + }, + "unlockPinSet": { + "message": "Unlock PIN set" + }, "authenticating": { "message": "Authenticating" }, @@ -4928,8 +5046,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": { @@ -5079,42 +5197,6 @@ "beta": { "message": "Beta" }, - "importantNotice": { - "message": "Important notice" - }, - "setupTwoStepLogin": { - "message": "Set up two-step login" - }, - "newDeviceVerificationNoticeContentPage1": { - "message": "Bitwarden will send a code to your account email to verify logins from new devices starting in February 2025." - }, - "newDeviceVerificationNoticeContentPage2": { - "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" - }, - "newDeviceVerificationNoticePageOneFormContent": { - "message": "Do you have reliable access to your email, $EMAIL$?", - "placeholders": { - "email": { - "content": "$1", - "example": "your_name@email.com" - } - } - }, - "newDeviceVerificationNoticePageOneEmailAccessNo": { - "message": "No, I do not" - }, - "newDeviceVerificationNoticePageOneEmailAccessYes": { - "message": "Yes, I can reliably access my email" - }, - "turnOnTwoStepLogin": { - "message": "Turn on two-step login" - }, - "changeAcctEmail": { - "message": "Change account email" - }, "extensionWidth": { "message": "Extension width" }, @@ -5169,6 +5251,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 +5283,98 @@ }, "secureDevicesBody": { "message": "Save unlimited passwords across unlimited devices with Bitwarden mobile, browser, and desktop apps." + }, + "nudgeBadgeAria": { + "message": "1 notification" + }, + "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!" + }, + "hasItemsVaultNudgeBodyOne": { + "message": "Autofill items for the current page" + }, + "hasItemsVaultNudgeBodyTwo": { + "message": "Favorite items for easy access" + }, + "hasItemsVaultNudgeBodyThree": { + "message": "Search your vault for something else" + }, + "newLoginNudgeTitle": { + "message": "Save time with autofill" + }, + "newLoginNudgeBodyOne": { + "message": "Include a", + "description": "This is in multiple parts to allow for bold text in the middle of the sentence.", + "example": "Include a Website so this login appears as an autofill suggestion." + }, + "newLoginNudgeBodyBold": { + "message": "Website", + "description": "This is in multiple parts to allow for bold text in the middle of the sentence.", + "example": "Include a Website so this login appears as an autofill suggestion." + }, + "newLoginNudgeBodyTwo": { + "message": "so this login appears as an autofill suggestion.", + "description": "This is in multiple parts to allow for bold text in the middle of the sentence.", + "example": "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" + }, + "newSshNudgeBodyOne": { + "message": "Store your keys and connect with the SSH agent for fast, encrypted authentication.", + "description": "Two part message", + "example": "Store your keys and connect with the SSH agent for fast, encrypted authentication. Learn more about SSH agent" + }, + "newSshNudgeBodyTwo": { + "message": "Learn more about SSH agent", + "description": "Two part message", + "example": "Store your keys and connect with the SSH agent for fast, encrypted authentication. Learn more about SSH agent" + }, + "generatorNudgeTitle": { + "message": "Quickly create passwords" + }, + "generatorNudgeBodyOne": { + "message": "Easily create strong and unique passwords by clicking on", + "description": "Two part message", + "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." + }, + "generatorNudgeBodyTwo": { + "message": "to help you keep your logins secure.", + "description": "Two part message", + "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." + }, + "generatorNudgeBodyAria": { + "message": "Easily create strong and unique passwords by clicking on the Generate password button to help you keep your logins secure.", + "description": "Aria label for the body content of the generator nudge" + }, + "noPermissionsViewPage": { + "message": "You do not have permissions to view this page. Try logging in with a different account." } } diff --git a/apps/browser/src/_locales/tr/messages.json b/apps/browser/src/_locales/tr/messages.json index 8a00287dfcc..c31f3507c54 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,55 @@ "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.", + "notificationNewItemAria": { + "message": "Yeni kayıt (yeni pencerede açılır)", + "description": "Aria label for the new item button in notification bar confirmation message when error is prompted" + }, + "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": { - "content": "$1" + "label": { + "content": "$1", + "example": "Login" } - }, - "description": "Shown to user after login is updated." + } + }, + "notificationLoginSaveConfirmation": { + "message": "Bitwarden’a kaydedildi.", + "description": "Shown to user after item is saved." + }, + "notificationLoginUpdatedConfirmation": { + "message": "Bitwarden’da güncellendi.", + "description": "Shown to user after item is updated." + }, + "selectItemAriaLabel": { + "message": "Seç: $ITEMTYPE$, $ITEMNAME$", + "description": "Used by screen readers. $1 is the item type (like vault or folder), $2 is the selected item name.", + "placeholders": { + "itemType": { + "content": "$1" + }, + "itemName": { + "content": "$2" + } + } }, "saveAsNewLoginAction": { "message": "Yeni hesap olarak kaydet", @@ -1082,12 +1120,16 @@ "message": "Hesabı güncelle", "description": "Button text for updating an existing login entry." }, - "saveLoginPrompt": { - "message": "Hesap kaydedilsin mi?", + "unlockToSave": { + "message": "Bu hesabı kaydetmek için kilidi açın", + "description": "User prompt to take action in order to save the login they just entered." + }, + "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": { @@ -1323,8 +1365,8 @@ "featureUnavailable": { "message": "Özellik kullanılamıyor" }, - "encryptionKeyMigrationRequired": { - "message": "Şifreleme anahtarınızın güncellenmesi gerekiyor. Şifreleme anahtarınızı güncellemek için lütfen web kasasına giriş yapın." + "legacyEncryptionUnsupported": { + "message": "Legacy encryption is no longer supported. Please contact support to recover your account." }, "premiumMembership": { "message": "Premium üyelik" @@ -1557,6 +1599,24 @@ "autofillSuggestionsSectionTitle": { "message": "Otomatik doldurma önerileri" }, + "autofillSpotlightTitle": { + "message": "Otomatik doldurma önerilerini kolayca bulun" + }, + "autofillSpotlightDesc": { + "message": "Bitwarden ile çakışmaması için tarayıcınızın otomatik doldurma ayarını kapatın." + }, + "turnOffBrowserAutofill": { + "message": "$BROWSER$ ile otomatik doldurmayı kapat", + "placeholders": { + "browser": { + "content": "$1", + "example": "Chrome" + } + } + }, + "turnOffAutofill": { + "message": "Otomatik doldurmayı kapat" + }, "showInlineMenuLabel": { "message": "Form alanlarında otomatik doldurma önerilerini göster" }, @@ -2144,6 +2204,9 @@ "useThisPassword": { "message": "Bu parolayı kullan" }, + "useThisPassphrase": { + "message": "Use this passphrase" + }, "useThisUsername": { "message": "Bu kullanıcı adını kullan" }, @@ -2167,15 +2230,6 @@ "vaultTimeoutAction1": { "message": "Zaman aşımı eylemi" }, - "newCustomizationOptionsCalloutTitle": { - "message": "Yeni özelleştirme seçenekleri" - }, - "newCustomizationOptionsCalloutContent": { - "message": "Customize your vault experience with quick copy actions, compact mode, and more!" - }, - "newCustomizationOptionsCalloutLink": { - "message": "Görünüm ayarlarının hepsini göster" - }, "lock": { "message": "Kilitle", "description": "Verb form: to make secure or inaccessible by" @@ -2623,6 +2677,10 @@ "message": "Tüm Send'ler", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, + "maxAccessCountReached": { + "message": "Maksimum erişim sayısına ulaşıldı", + "description": "This text will be displayed after a Send has been accessed the maximum amount of times." + }, "hideTextByDefault": { "message": "Metni varsayılan olarak gizle" }, @@ -2963,14 +3021,14 @@ "copyCustomFieldNameNotUnique": { "message": "Benzersiz tanımlayıcı bulunamadı." }, - "convertOrganizationEncryptionDesc": { - "message": "$ORGANIZATION$ kendi barındırdığı bir anahtar sunucusuyla SSO kullanıyor. Bu kuruluşun üyelerinin artık ana parola kullanması gerekmiyor.", - "placeholders": { - "organization": { - "content": "$1", - "example": "My Org Name" - } - } + "removeMasterPasswordForOrganizationUserKeyConnector": { + "message": "A master password is no longer required for members of the following organization. Please confirm the domain below with your organization administrator." + }, + "organizationName": { + "message": "Kuruluş adı" + }, + "keyConnectorDomain": { + "message": "Key Connector alan adı" }, "leaveOrganization": { "message": "Kuruluştan ayrıl" @@ -3532,12 +3590,36 @@ "deviceTrusted": { "message": "Cihaza güvenildi" }, - "sendsNoItemsTitle": { - "message": "Aktif Send yok", + "trustOrganization": { + "message": "Trust organization" + }, + "trust": { + "message": "Trust" + }, + "doNotTrust": { + "message": "Güvenme" + }, + "organizationNotTrusted": { + "message": "Kuruluş güvenilir değil" + }, + "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" + }, + "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." + }, + "orgTrustWarning1": { + "message": "This organization has an Enterprise policy that will enroll you in account recovery. Enrollment will allow organization administrators to change your password. Only proceed if you recognize this organization and the fingerprint phrase displayed below matches the organization's fingerprint." + }, + "trustUser": { + "message": "Trust user" + }, + "sendsTitleNoItems": { + "message": "Hassas bilgileri güvenle paylaşın", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, - "sendsNoItemsMessage": { - "message": "Şifrelenmiş bilgileri güvenle paylaşmak için Send'i kullanabilirsiniz.", + "sendsBodyNoItems": { + "message": "Share files and data securely with anyone, on any platform. Your information will remain end-to-end encrypted while limiting exposure.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "inputRequired": { @@ -4456,6 +4538,33 @@ } } }, + "downloadBitwarden": { + "message": "Bitwarden’ı indirin" + }, + "downloadBitwardenOnAllDevices": { + "message": "Bitwarden’ı tüm cihazlarınıza indirin" + }, + "getTheMobileApp": { + "message": "Mobil uygulamayı indirin" + }, + "getTheMobileAppDesc": { + "message": "Parolalarınıza Bitwarden mobil uygulaması sayesinde her yerden ulaşın." + }, + "getTheDesktopApp": { + "message": "Masaüstü uygulamasını indirin" + }, + "getTheDesktopAppDesc": { + "message": "Kasanıza tarayıcıyı kullanmadan erişin, biyometri ile kilit açmayı etkinleştirerek hem masaüstü uygulamasından hem de tarayıcı uzantısından kilit açmayı geliştirin." + }, + "downloadFromBitwardenNow": { + "message": "Şimdi bitwarden.com’dan indirin" + }, + "getItOnGooglePlay": { + "message": "Google Play'den edinin" + }, + "downloadOnTheAppStore": { + "message": "App Store'dan indirin" + }, "permanentlyDeleteAttachmentConfirmation": { "message": "Bu dosyayı kalıcı olarak silmek istediğinizden emin misiniz?" }, @@ -4917,6 +5026,15 @@ "biometricsStatusHelptextUnavailableReasonUnknown": { "message": "Biometric unlock is currently unavailable for an unknown reason." }, + "unlockVault": { + "message": "Kasanızın kilidini saniyeler içinde açın" + }, + "unlockVaultDesc": { + "message": "Kasanıza daha hızlı ulaşmak için kilit açma ve zaman aşımı ayarlarınızı özelleştirebilirsiniz." + }, + "unlockPinSet": { + "message": "Unlock PIN set" + }, "authenticating": { "message": "Kimlik doğrulanıyor" }, @@ -4928,8 +5046,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": { @@ -5079,42 +5197,6 @@ "beta": { "message": "Beta" }, - "importantNotice": { - "message": "Önemli uyarı" - }, - "setupTwoStepLogin": { - "message": "İki adımlı girişi ayarla" - }, - "newDeviceVerificationNoticeContentPage1": { - "message": "Şubat 2025 itibarıyla Bitwarden, yeni cihazlardan yeni girişleri doğrulamanız için e-posta adresinize bir kod gönderecektir." - }, - "newDeviceVerificationNoticeContentPage2": { - "message": "Hesabınızı korumanın alternatif bir yolu olarak iki adımlı girişi etkinleştirebilirsiniz. Aksi halde e-posta adresinizin doğru olduğundan emin olmalısınız." - }, - "remindMeLater": { - "message": "Daha sonra hatırlat" - }, - "newDeviceVerificationNoticePageOneFormContent": { - "message": "$EMAIL$ adresinize sağlıklı bir şekilde erişebiliyor musunuz?", - "placeholders": { - "email": { - "content": "$1", - "example": "your_name@email.com" - } - } - }, - "newDeviceVerificationNoticePageOneEmailAccessNo": { - "message": "Hayır, erişemiyorum" - }, - "newDeviceVerificationNoticePageOneEmailAccessYes": { - "message": "Evet, e-postalarıma sağlıklı bir şekilde erişebiliyorum" - }, - "turnOnTwoStepLogin": { - "message": "İki adımlı girişi etkinleştir" - }, - "changeAcctEmail": { - "message": "Hesap e-postasını değiştir" - }, "extensionWidth": { "message": "Uzantı genişliği" }, @@ -5169,6 +5251,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 +5283,98 @@ }, "secureDevicesBody": { "message": "Bitwarden mobil, tarayıcı ve masaüstü uygulamalarıyla istediğiniz kadar cihaza istediğiniz kadar parola kaydedebilirsiniz." + }, + "nudgeBadgeAria": { + "message": "1 bildirim" + }, + "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!" + }, + "hasItemsVaultNudgeBodyOne": { + "message": "Autofill items for the current page" + }, + "hasItemsVaultNudgeBodyTwo": { + "message": "Favorite items for easy access" + }, + "hasItemsVaultNudgeBodyThree": { + "message": "Search your vault for something else" + }, + "newLoginNudgeTitle": { + "message": "Otomatik doldurmayla zaman kazanın" + }, + "newLoginNudgeBodyOne": { + "message": "Bu hesabın otomatik doldurma önerisi olarak görünmesi için bir", + "description": "This is in multiple parts to allow for bold text in the middle of the sentence.", + "example": "Include a Website so this login appears as an autofill suggestion." + }, + "newLoginNudgeBodyBold": { + "message": "web sitesi", + "description": "This is in multiple parts to allow for bold text in the middle of the sentence.", + "example": "Include a Website so this login appears as an autofill suggestion." + }, + "newLoginNudgeBodyTwo": { + "message": "ekleyin.", + "description": "This is in multiple parts to allow for bold text in the middle of the sentence.", + "example": "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" + }, + "newSshNudgeBodyOne": { + "message": "Store your keys and connect with the SSH agent for fast, encrypted authentication.", + "description": "Two part message", + "example": "Store your keys and connect with the SSH agent for fast, encrypted authentication. Learn more about SSH agent" + }, + "newSshNudgeBodyTwo": { + "message": "Learn more about SSH agent", + "description": "Two part message", + "example": "Store your keys and connect with the SSH agent for fast, encrypted authentication. Learn more about SSH agent" + }, + "generatorNudgeTitle": { + "message": "Quickly create passwords" + }, + "generatorNudgeBodyOne": { + "message": "Easily create strong and unique passwords by clicking on", + "description": "Two part message", + "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." + }, + "generatorNudgeBodyTwo": { + "message": "to help you keep your logins secure.", + "description": "Two part message", + "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." + }, + "generatorNudgeBodyAria": { + "message": "Easily create strong and unique passwords by clicking on the Generate password button to help you keep your logins secure.", + "description": "Aria label for the body content of the generator nudge" + }, + "noPermissionsViewPage": { + "message": "Bu sayfayı görüntüleme izniniz yok. Farklı bir hesapla giriş yapmayı deneyin." } } diff --git a/apps/browser/src/_locales/uk/messages.json b/apps/browser/src/_locales/uk/messages.json index bdd96ba9ea4..141de06e0a9 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,55 @@ "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.", + "notificationNewItemAria": { + "message": "Новий запис, відкривається у новому вікні", + "description": "Aria label for the new item button in notification bar confirmation message when error is prompted" + }, + "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": { - "content": "$1" + "label": { + "content": "$1", + "example": "Login" } - }, - "description": "Shown to user after login is updated." + } + }, + "notificationLoginSaveConfirmation": { + "message": "збережено до Bitwarden.", + "description": "Shown to user after item is saved." + }, + "notificationLoginUpdatedConfirmation": { + "message": "оновлено в Bitwarden.", + "description": "Shown to user after item is updated." + }, + "selectItemAriaLabel": { + "message": "Вибрати $ITEMTYPE$, $ITEMNAME$", + "description": "Used by screen readers. $1 is the item type (like vault or folder), $2 is the selected item name.", + "placeholders": { + "itemType": { + "content": "$1" + }, + "itemName": { + "content": "$2" + } + } }, "saveAsNewLoginAction": { "message": "Зберегти як новий запис", @@ -1082,12 +1120,16 @@ "message": "Оновити запис", "description": "Button text for updating an existing login entry." }, - "saveLoginPrompt": { - "message": "Зберегти запис?", + "unlockToSave": { + "message": "Розблокуйте, щоб зберегти цей запис", + "description": "User prompt to take action in order to save the login they just entered." + }, + "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": { @@ -1323,8 +1365,8 @@ "featureUnavailable": { "message": "Функція недоступна" }, - "encryptionKeyMigrationRequired": { - "message": "Потрібно перенести ключ шифрування. Увійдіть у вебсховище та оновіть свій ключ шифрування." + "legacyEncryptionUnsupported": { + "message": "Legacy encryption is no longer supported. Please contact support to recover your account." }, "premiumMembership": { "message": "Преміум статус" @@ -1557,6 +1599,24 @@ "autofillSuggestionsSectionTitle": { "message": "Пропозиції автозаповнення" }, + "autofillSpotlightTitle": { + "message": "Користуйтеся пропозиціями автозаповнення" + }, + "autofillSpotlightDesc": { + "message": "Вимкніть налаштування автозаповнення вашого браузера, щоб вони не конфліктували з Bitwarden." + }, + "turnOffBrowserAutofill": { + "message": "Вимкнути автозаповнення $BROWSER$", + "placeholders": { + "browser": { + "content": "$1", + "example": "Chrome" + } + } + }, + "turnOffAutofill": { + "message": "Вимкніть автозаповнення" + }, "showInlineMenuLabel": { "message": "Пропозиції автозаповнення на полях форм" }, @@ -2144,6 +2204,9 @@ "useThisPassword": { "message": "Використати цей пароль" }, + "useThisPassphrase": { + "message": "Use this passphrase" + }, "useThisUsername": { "message": "Використати це ім'я користувача" }, @@ -2167,15 +2230,6 @@ "vaultTimeoutAction1": { "message": "Дія після часу очікування" }, - "newCustomizationOptionsCalloutTitle": { - "message": "Нові можливості налаштування" - }, - "newCustomizationOptionsCalloutContent": { - "message": "Налаштуйте своє сховище за допомогою швидких дій копіювання, компактного режиму та інших можливостей!" - }, - "newCustomizationOptionsCalloutLink": { - "message": "Всі налаштування подання" - }, "lock": { "message": "Блокувати", "description": "Verb form: to make secure or inaccessible by" @@ -2623,6 +2677,10 @@ "message": "Усі відправлення", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, + "maxAccessCountReached": { + "message": "Max access count reached", + "description": "This text will be displayed after a Send has been accessed the maximum amount of times." + }, "hideTextByDefault": { "message": "Типово приховувати текст" }, @@ -2963,14 +3021,14 @@ "copyCustomFieldNameNotUnique": { "message": "Не знайдено унікальний ідентифікатор." }, - "convertOrganizationEncryptionDesc": { - "message": "$ORGANIZATION$ використовує SSO з власним сервером ключів. Головний пароль для учасників цієї організації більше не вимагається.", - "placeholders": { - "organization": { - "content": "$1", - "example": "My Org Name" - } - } + "removeMasterPasswordForOrganizationUserKeyConnector": { + "message": "Головний пароль більше не є обов'язковим для учасників зазначеної організації. Підтвердьте вказаний нижче домен з адміністратором вашої організації." + }, + "organizationName": { + "message": "Назва організації" + }, + "keyConnectorDomain": { + "message": "Домен Key Connector" }, "leaveOrganization": { "message": "Покинути організацію" @@ -3006,7 +3064,7 @@ } }, "exportingIndividualVaultWithAttachmentsDescription": { - "message": "Будуть експортовані лише записи особистого сховища, включно з вкладеннями, пов'язані з $EMAIL$. Записи сховища організації не експортуватимуться.", + "message": "Будуть експортовані лише записи особистого сховища включно з вкладеннями, пов'язані з $EMAIL$. Записи сховища організації не експортуватимуться.", "placeholders": { "email": { "content": "$1", @@ -3532,12 +3590,36 @@ "deviceTrusted": { "message": "Довірений пристрій" }, - "sendsNoItemsTitle": { - "message": "Немає активних відправлень", + "trustOrganization": { + "message": "Довіряти організації" + }, + "trust": { + "message": "Довіряти" + }, + "doNotTrust": { + "message": "Не довіряти" + }, + "organizationNotTrusted": { + "message": "Це не довірена організація" + }, + "emergencyAccessTrustWarning": { + "message": "Щоб захистити свій обліковий запис, підтверджуйте лише якщо ви надали цьому користувачу екстрений доступ, і його цифровий відбиток збігається з показаним в його обліковому записі." + }, + "orgTrustWarning": { + "message": "Щоб захистити свій обліковий запис, продовжуйте лише якщо ви є учасником цієї організації, маєте ввімкнене відновлення облікового запису, і показаний нижче цифровий відбиток збігається з відбитком організації." + }, + "orgTrustWarning1": { + "message": "Ця організація має політику компанії, яка розгорне для вас відновлення облікового запису. Розгортання дасть змогу адміністраторам організації змінювати ваш пароль. Продовжуйте тільки якщо ви впізнаєте організацію і наведена нижче фраза відбитка відповідає відбитку організації." + }, + "trustUser": { + "message": "Довіряти користувачу" + }, + "sendsTitleNoItems": { + "message": "Безпечно надсилайте конфіденційну інформацію", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, - "sendsNoItemsMessage": { - "message": "Використовуйте відправлення, щоб безпечно надавати доступ іншим до зашифрованої інформації.", + "sendsBodyNoItems": { + "message": "Безпечно діліться файлами й даними з ким завгодно, на будь-якій платформі. Ваша інформація наскрізно зашифрована та має обмежений доступ.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "inputRequired": { @@ -4456,6 +4538,33 @@ } } }, + "downloadBitwarden": { + "message": "Завантажити Bitwarden" + }, + "downloadBitwardenOnAllDevices": { + "message": "Завантажити Bitwarden на всіх пристроях" + }, + "getTheMobileApp": { + "message": "Отримати програму для мобільного пристрою" + }, + "getTheMobileAppDesc": { + "message": "Користуйтеся своїми паролями звідусіль за допомогою програми Bitwarden для мобільних пристроїв." + }, + "getTheDesktopApp": { + "message": "Отримати програму для комп'ютера" + }, + "getTheDesktopAppDesc": { + "message": "Користуйтеся сховищем без браузера, налаштуйте біометричне розблокування, щоб спростити доступ у програмі на комп'ютері та в розширенні браузера." + }, + "downloadFromBitwardenNow": { + "message": "Завантажити з bitwarden.com" + }, + "getItOnGooglePlay": { + "message": "Завантажити з Google Play" + }, + "downloadOnTheAppStore": { + "message": "Завантажити з App Store" + }, "permanentlyDeleteAttachmentConfirmation": { "message": "Ви дійсно хочете остаточно видалити це вкладення?" }, @@ -4917,6 +5026,15 @@ "biometricsStatusHelptextUnavailableReasonUnknown": { "message": "Біометричне розблокування зараз недоступне з невідомої причини." }, + "unlockVault": { + "message": "Розблоковуйте сховище за секунди" + }, + "unlockVaultDesc": { + "message": "Ви можете налаштувати розблокування і час очікування для швидшого доступу до сховища." + }, + "unlockPinSet": { + "message": "Розблокування PIN-кодом встановлено" + }, "authenticating": { "message": "Аутентифікація" }, @@ -4928,8 +5046,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": { @@ -5079,42 +5197,6 @@ "beta": { "message": "Бета" }, - "importantNotice": { - "message": "Важлива інформація" - }, - "setupTwoStepLogin": { - "message": "Налаштувати двоетапну перевірку" - }, - "newDeviceVerificationNoticeContentPage1": { - "message": "Bitwarden надсилатиме код підтвердження на електронну пошту вашого облікового запису під час входу з нових пристроїв, починаючи з лютого 2025 року." - }, - "newDeviceVerificationNoticeContentPage2": { - "message": "Ви можете налаштувати двоетапну перевірку як альтернативний спосіб захисту свого облікового запису, або змінити електронну пошту на таку, до якої ви маєте доступ." - }, - "remindMeLater": { - "message": "Нагадати пізніше" - }, - "newDeviceVerificationNoticePageOneFormContent": { - "message": "Ви маєте постійний доступ до своєї електронної пошти $EMAIL$?", - "placeholders": { - "email": { - "content": "$1", - "example": "your_name@email.com" - } - } - }, - "newDeviceVerificationNoticePageOneEmailAccessNo": { - "message": "Ні, не маю" - }, - "newDeviceVerificationNoticePageOneEmailAccessYes": { - "message": "Так, я маю постійний доступ до своєї електронної пошти" - }, - "turnOnTwoStepLogin": { - "message": "Увімкнути двоетапну перевірку" - }, - "changeAcctEmail": { - "message": "Змінити адресу е-пошти" - }, "extensionWidth": { "message": "Ширина вікна розширення" }, @@ -5169,6 +5251,12 @@ "changeAtRiskPassword": { "message": "Змінити ризикований пароль" }, + "settingsVaultOptions": { + "message": "Параметри сховища" + }, + "emptyVaultDescription": { + "message": "Сховище захищає не лише ваші паролі. Безпечно зберігайте дані для входу, посвідчення, картки й нотатки." + }, "introCarouselLabel": { "message": "Вітаємо в Bitwarden" }, @@ -5195,5 +5283,98 @@ }, "secureDevicesBody": { "message": "Зберігайте скільки завгодно паролів на необмеженій кількості пристроїв, використовуючи Bitwarden для мобільних пристроїв, браузерів та комп'ютерів." + }, + "nudgeBadgeAria": { + "message": "1 сповіщення" + }, + "emptyVaultNudgeTitle": { + "message": "Імпортуйте наявні паролі" + }, + "emptyVaultNudgeBody": { + "message": "Скористайтеся інструментом імпортування, щоб швидко перенести записи до Bitwarden, а не додавати їх вручну." + }, + "emptyVaultNudgeButton": { + "message": "Імпортувати" + }, + "hasItemsVaultNudgeTitle": { + "message": "Вітаємо у вашому сховищі!" + }, + "hasItemsVaultNudgeBodyOne": { + "message": "Автозаповнення записів для поточної сторінки" + }, + "hasItemsVaultNudgeBodyTwo": { + "message": "Обрані записи для швидкого доступу" + }, + "hasItemsVaultNudgeBodyThree": { + "message": "Пошук інших елементів у сховищі" + }, + "newLoginNudgeTitle": { + "message": "Заощаджуйте час з автозаповненням" + }, + "newLoginNudgeBodyOne": { + "message": "Включити", + "description": "This is in multiple parts to allow for bold text in the middle of the sentence.", + "example": "Include a Website so this login appears as an autofill suggestion." + }, + "newLoginNudgeBodyBold": { + "message": "Вебсайт", + "description": "This is in multiple parts to allow for bold text in the middle of the sentence.", + "example": "Include a Website so this login appears as an autofill suggestion." + }, + "newLoginNudgeBodyTwo": { + "message": "щоб цей запис з'являвся у пропозиціях автозаповнення.", + "description": "This is in multiple parts to allow for bold text in the middle of the sentence.", + "example": "Include a Website so this login appears as an autofill suggestion." + }, + "newCardNudgeTitle": { + "message": "Швидке оформлення замовлень" + }, + "newCardNudgeBody": { + "message": "За допомогою записів для карток можна безпечно й точно заповнювати платіжні форми." + }, + "newIdentityNudgeTitle": { + "message": "Спростіть створення облікових записів" + }, + "newIdentityNudgeBody": { + "message": "За допомогою записів для посвідчень можна швидко заповнювати форми реєстрації чи контактів." + }, + "newNoteNudgeTitle": { + "message": "Захистіть свої конфіденційні дані" + }, + "newNoteNudgeBody": { + "message": "За допомогою нотаток можна надійно зберігати конфіденційні дані, як-от банківську інформацію або страхування." + }, + "newSshNudgeTitle": { + "message": "SSH-доступ для розробників" + }, + "newSshNudgeBodyOne": { + "message": "Зберігайте свої ключі та під'єднуйтеся за допомогою SSH-агента для швидкої зашифрованої автентифікації.", + "description": "Two part message", + "example": "Store your keys and connect with the SSH agent for fast, encrypted authentication. Learn more about SSH agent" + }, + "newSshNudgeBodyTwo": { + "message": "Докладніше про SSH-агента", + "description": "Two part message", + "example": "Store your keys and connect with the SSH agent for fast, encrypted authentication. Learn more about SSH agent" + }, + "generatorNudgeTitle": { + "message": "Швидко створюйте паролі" + }, + "generatorNudgeBodyOne": { + "message": "Легко створюйте надійні та унікальні паролі, натиснувши на", + "description": "Two part message", + "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." + }, + "generatorNudgeBodyTwo": { + "message": "щоб зберегти свої записи в безпеці.", + "description": "Two part message", + "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." + }, + "generatorNudgeBodyAria": { + "message": "Легко створюйте надійні та унікальні паролі, натиснувши кнопку Генерувати пароль, щоб зберегти свої записи в безпеці.", + "description": "Aria label for the body content of the generator nudge" + }, + "noPermissionsViewPage": { + "message": "У вас немає дозволу переглядати цю сторінку. Спробуйте ввійти з іншим обліковим записом." } } diff --git a/apps/browser/src/_locales/vi/messages.json b/apps/browser/src/_locales/vi/messages.json index c6c6ed7ee6d..6af7ae6df41 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,55 @@ "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.", + "notificationNewItemAria": { + "message": "New Item, opens in new window", + "description": "Aria label for the new item button in notification bar confirmation message when error is prompted" + }, + "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": { - "content": "$1" + "label": { + "content": "$1", + "example": "Login" } - }, - "description": "Shown to user after login is updated." + } + }, + "notificationLoginSaveConfirmation": { + "message": "saved to Bitwarden.", + "description": "Shown to user after item is saved." + }, + "notificationLoginUpdatedConfirmation": { + "message": "updated in Bitwarden.", + "description": "Shown to user after item is updated." + }, + "selectItemAriaLabel": { + "message": "Select $ITEMTYPE$, $ITEMNAME$", + "description": "Used by screen readers. $1 is the item type (like vault or folder), $2 is the selected item name.", + "placeholders": { + "itemType": { + "content": "$1" + }, + "itemName": { + "content": "$2" + } + } }, "saveAsNewLoginAction": { "message": "Save as new login", @@ -1082,12 +1120,16 @@ "message": "Update login", "description": "Button text for updating an existing login entry." }, - "saveLoginPrompt": { - "message": "Save login?", + "unlockToSave": { + "message": "Unlock to save this login", + "description": "User prompt to take action in order to save the login they just entered." + }, + "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": { @@ -1323,8 +1365,8 @@ "featureUnavailable": { "message": "Tính năng không có sẵn" }, - "encryptionKeyMigrationRequired": { - "message": "Cần di chuyển khóa mã hóa. Vui lòng đăng nhập trang web Bitwaden để cập nhật khóa mã hóa của bạn." + "legacyEncryptionUnsupported": { + "message": "Legacy encryption is no longer supported. Please contact support to recover your account." }, "premiumMembership": { "message": "Thành viên Cao Cấp" @@ -1557,6 +1599,24 @@ "autofillSuggestionsSectionTitle": { "message": "Các gợi ý điền tự động" }, + "autofillSpotlightTitle": { + "message": "Easily find autofill suggestions" + }, + "autofillSpotlightDesc": { + "message": "Turn off your browser's autofill settings, so they don't conflict with Bitwarden." + }, + "turnOffBrowserAutofill": { + "message": "Turn off $BROWSER$ autofill", + "placeholders": { + "browser": { + "content": "$1", + "example": "Chrome" + } + } + }, + "turnOffAutofill": { + "message": "Turn off autofill" + }, "showInlineMenuLabel": { "message": "Hiển thị các gợi ý tự động điền trên các trường biểu mẫu" }, @@ -2144,6 +2204,9 @@ "useThisPassword": { "message": "Use this password" }, + "useThisPassphrase": { + "message": "Use this passphrase" + }, "useThisUsername": { "message": "Use this username" }, @@ -2167,15 +2230,6 @@ "vaultTimeoutAction1": { "message": "Timeout action" }, - "newCustomizationOptionsCalloutTitle": { - "message": "New customization options" - }, - "newCustomizationOptionsCalloutContent": { - "message": "Customize your vault experience with quick copy actions, compact mode, and more!" - }, - "newCustomizationOptionsCalloutLink": { - "message": "View all Appearance settings" - }, "lock": { "message": "Khóa", "description": "Verb form: to make secure or inaccessible by" @@ -2623,6 +2677,10 @@ "message": "Tất cả mục Gửi", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, + "maxAccessCountReached": { + "message": "Max access count reached", + "description": "This text will be displayed after a Send has been accessed the maximum amount of times." + }, "hideTextByDefault": { "message": "Hide text by default" }, @@ -2963,14 +3021,14 @@ "copyCustomFieldNameNotUnique": { "message": "Không tìm thấy danh tính duy nhất." }, - "convertOrganizationEncryptionDesc": { - "message": "$ORGANIZATION$ đang sử dụng SSO với khóa máy chủ tự lưu trữ. Mật khẩu chính không còn cần để đăng nhập cho các thành viên của tổ chức này.", - "placeholders": { - "organization": { - "content": "$1", - "example": "My Org Name" - } - } + "removeMasterPasswordForOrganizationUserKeyConnector": { + "message": "A master password is no longer required for members of the following organization. Please confirm the domain below with your organization administrator." + }, + "organizationName": { + "message": "Organization name" + }, + "keyConnectorDomain": { + "message": "Key Connector domain" }, "leaveOrganization": { "message": "Rời tổ chức" @@ -3532,12 +3590,36 @@ "deviceTrusted": { "message": "Thiết bị tin cậy" }, - "sendsNoItemsTitle": { - "message": "Không có mục Gửi nào đang hoạt động", + "trustOrganization": { + "message": "Trust organization" + }, + "trust": { + "message": "Trust" + }, + "doNotTrust": { + "message": "Do not trust" + }, + "organizationNotTrusted": { + "message": "Organization is not trusted" + }, + "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" + }, + "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." + }, + "orgTrustWarning1": { + "message": "This organization has an Enterprise policy that will enroll you in account recovery. Enrollment will allow organization administrators to change your password. Only proceed if you recognize this organization and the fingerprint phrase displayed below matches the organization's fingerprint." + }, + "trustUser": { + "message": "Trust user" + }, + "sendsTitleNoItems": { + "message": "Send sensitive information safely", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, - "sendsNoItemsMessage": { - "message": "Sử dụng Gửi để chia sẻ thông tin mã hóa một cách an toàn với bất kỳ ai.", + "sendsBodyNoItems": { + "message": "Share files and data securely with anyone, on any platform. Your information will remain end-to-end encrypted while limiting exposure.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "inputRequired": { @@ -4456,6 +4538,33 @@ } } }, + "downloadBitwarden": { + "message": "Download Bitwarden" + }, + "downloadBitwardenOnAllDevices": { + "message": "Download Bitwarden on all devices" + }, + "getTheMobileApp": { + "message": "Get the mobile app" + }, + "getTheMobileAppDesc": { + "message": "Access your passwords on the go with the Bitwarden mobile app." + }, + "getTheDesktopApp": { + "message": "Get the desktop app" + }, + "getTheDesktopAppDesc": { + "message": "Access your vault without a browser, then set up unlock with biometrics to expedite unlocking in both the desktop app and browser extension." + }, + "downloadFromBitwardenNow": { + "message": "Download from bitwarden.com now" + }, + "getItOnGooglePlay": { + "message": "Get it on Google Play" + }, + "downloadOnTheAppStore": { + "message": "Download on the App Store" + }, "permanentlyDeleteAttachmentConfirmation": { "message": "Bạn có chắc chắn muốn xóa vĩnh viễn tệp đính kèm này không?" }, @@ -4917,6 +5026,15 @@ "biometricsStatusHelptextUnavailableReasonUnknown": { "message": "Biometric unlock is currently unavailable for an unknown reason." }, + "unlockVault": { + "message": "Unlock your vault in seconds" + }, + "unlockVaultDesc": { + "message": "You can customize your unlock and timeout settings to more quickly access your vault." + }, + "unlockPinSet": { + "message": "Unlock PIN set" + }, "authenticating": { "message": "Authenticating" }, @@ -4928,8 +5046,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": { @@ -5079,42 +5197,6 @@ "beta": { "message": "Beta" }, - "importantNotice": { - "message": "Thông báo quan trọng" - }, - "setupTwoStepLogin": { - "message": "Thiết lập đăng nhập hai bước" - }, - "newDeviceVerificationNoticeContentPage1": { - "message": "Bitwarden will send a code to your account email to verify logins from new devices starting in February 2025." - }, - "newDeviceVerificationNoticeContentPage2": { - "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": "Nhắc sau" - }, - "newDeviceVerificationNoticePageOneFormContent": { - "message": "Do you have reliable access to your email, $EMAIL$?", - "placeholders": { - "email": { - "content": "$1", - "example": "your_name@email.com" - } - } - }, - "newDeviceVerificationNoticePageOneEmailAccessNo": { - "message": "Không, tôi không có" - }, - "newDeviceVerificationNoticePageOneEmailAccessYes": { - "message": "Có, tôi có quyền truy cập email này" - }, - "turnOnTwoStepLogin": { - "message": "Turn on two-step login" - }, - "changeAcctEmail": { - "message": "Đổi email tài khoản" - }, "extensionWidth": { "message": "Extension width" }, @@ -5169,6 +5251,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 +5283,98 @@ }, "secureDevicesBody": { "message": "Save unlimited passwords across unlimited devices with Bitwarden mobile, browser, and desktop apps." + }, + "nudgeBadgeAria": { + "message": "1 notification" + }, + "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!" + }, + "hasItemsVaultNudgeBodyOne": { + "message": "Autofill items for the current page" + }, + "hasItemsVaultNudgeBodyTwo": { + "message": "Favorite items for easy access" + }, + "hasItemsVaultNudgeBodyThree": { + "message": "Search your vault for something else" + }, + "newLoginNudgeTitle": { + "message": "Save time with autofill" + }, + "newLoginNudgeBodyOne": { + "message": "Include a", + "description": "This is in multiple parts to allow for bold text in the middle of the sentence.", + "example": "Include a Website so this login appears as an autofill suggestion." + }, + "newLoginNudgeBodyBold": { + "message": "Website", + "description": "This is in multiple parts to allow for bold text in the middle of the sentence.", + "example": "Include a Website so this login appears as an autofill suggestion." + }, + "newLoginNudgeBodyTwo": { + "message": "so this login appears as an autofill suggestion.", + "description": "This is in multiple parts to allow for bold text in the middle of the sentence.", + "example": "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" + }, + "newSshNudgeBodyOne": { + "message": "Store your keys and connect with the SSH agent for fast, encrypted authentication.", + "description": "Two part message", + "example": "Store your keys and connect with the SSH agent for fast, encrypted authentication. Learn more about SSH agent" + }, + "newSshNudgeBodyTwo": { + "message": "Learn more about SSH agent", + "description": "Two part message", + "example": "Store your keys and connect with the SSH agent for fast, encrypted authentication. Learn more about SSH agent" + }, + "generatorNudgeTitle": { + "message": "Quickly create passwords" + }, + "generatorNudgeBodyOne": { + "message": "Easily create strong and unique passwords by clicking on", + "description": "Two part message", + "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." + }, + "generatorNudgeBodyTwo": { + "message": "to help you keep your logins secure.", + "description": "Two part message", + "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." + }, + "generatorNudgeBodyAria": { + "message": "Easily create strong and unique passwords by clicking on the Generate password button to help you keep your logins secure.", + "description": "Aria label for the body content of the generator nudge" + }, + "noPermissionsViewPage": { + "message": "You do not have permissions to view this page. Try logging in with a different account." } } diff --git a/apps/browser/src/_locales/zh_CN/messages.json b/apps/browser/src/_locales/zh_CN/messages.json index 662284568cd..cfd35616fa9 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,55 @@ "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 中更新。", + "notificationNewItemAria": { + "message": "新增项目(在新窗口中打开)", + "description": "Aria label for the new item button in notification bar confirmation message when error is prompted" + }, + "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": { - "content": "$1" + "label": { + "content": "$1", + "example": "Login" } - }, - "description": "Shown to user after login is updated." + } + }, + "notificationLoginSaveConfirmation": { + "message": "已保存到 Bitwarden。", + "description": "Shown to user after item is saved." + }, + "notificationLoginUpdatedConfirmation": { + "message": "已更新到 Bitwarden。", + "description": "Shown to user after item is updated." + }, + "selectItemAriaLabel": { + "message": "选择 $ITEMTYPE$,$ITEMNAME$", + "description": "Used by screen readers. $1 is the item type (like vault or folder), $2 is the selected item name.", + "placeholders": { + "itemType": { + "content": "$1" + }, + "itemName": { + "content": "$2" + } + } }, "saveAsNewLoginAction": { "message": "保存为新的登录", @@ -1082,12 +1120,16 @@ "message": "更新登录", "description": "Button text for updating an existing login entry." }, - "saveLoginPrompt": { - "message": "保存登录吗?", + "unlockToSave": { + "message": "解锁以保存此登录", + "description": "User prompt to take action in order to save the login they just entered." + }, + "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 +1141,7 @@ "description": "Message displayed when login details are successfully updated." }, "loginUpdateTaskSuccess": { - "message": "干得好!您采取了使您和 $ORGANIZATION$ 更加安全的措施。", + "message": "干得好!您采取了使您和 $ORGANIZATION$ 更加安全的步骤。", "placeholders": { "organization": { "content": "$1" @@ -1323,8 +1365,8 @@ "featureUnavailable": { "message": "功能不可用" }, - "encryptionKeyMigrationRequired": { - "message": "需要迁移加密密钥。请登录网页版密码库来更新您的加密密钥。" + "legacyEncryptionUnsupported": { + "message": "旧版加密方式已不再受支持。请联系客服恢复您的账户。" }, "premiumMembership": { "message": "高级会员" @@ -1557,6 +1599,24 @@ "autofillSuggestionsSectionTitle": { "message": "自动填充建议" }, + "autofillSpotlightTitle": { + "message": "轻松找到自动填充建议" + }, + "autofillSpotlightDesc": { + "message": "关闭浏览器的自动填充设置,以免与 Bitwarden 产生冲突。" + }, + "turnOffBrowserAutofill": { + "message": "关闭 $BROWSER$ 的自动填充", + "placeholders": { + "browser": { + "content": "$1", + "example": "Chrome" + } + } + }, + "turnOffAutofill": { + "message": "关闭自动填充" + }, "showInlineMenuLabel": { "message": "在表单字段中显示自动填充建议" }, @@ -2144,18 +2204,21 @@ "useThisPassword": { "message": "使用此密码" }, + "useThisPassphrase": { + "message": "使用此密码短语" + }, "useThisUsername": { "message": "使用此用户名" }, "securePasswordGenerated": { - "message": "安全密码生成好了!别忘了也在网站上更新一下您的密码。" + "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": "创建一个强大且唯一的密码", + "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'" }, "vaultCustomization": { @@ -2167,15 +2230,6 @@ "vaultTimeoutAction1": { "message": "超时动作" }, - "newCustomizationOptionsCalloutTitle": { - "message": "新的自定义选项" - }, - "newCustomizationOptionsCalloutContent": { - "message": "自定义您的密码库体验,包括快速复制操作、紧凑模式等!" - }, - "newCustomizationOptionsCalloutLink": { - "message": "查看所有外观设置" - }, "lock": { "message": "锁定", "description": "Verb form: to make secure or inaccessible by" @@ -2623,6 +2677,10 @@ "message": "所有 Send", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, + "maxAccessCountReached": { + "message": "已达最大访问次数", + "description": "This text will be displayed after a Send has been accessed the maximum amount of times." + }, "hideTextByDefault": { "message": "默认隐藏文本" }, @@ -2963,14 +3021,14 @@ "copyCustomFieldNameNotUnique": { "message": "未找到唯一的标识符。" }, - "convertOrganizationEncryptionDesc": { - "message": "$ORGANIZATION$ 使用自托管密钥服务器 SSO。这个组织的成员登录时将不再需要主密码。", - "placeholders": { - "organization": { - "content": "$1", - "example": "My Org Name" - } - } + "removeMasterPasswordForOrganizationUserKeyConnector": { + "message": "以下组织的成员不再需要主密码。请与您的组织管理员确认下面的域名。" + }, + "organizationName": { + "message": "组织名称" + }, + "keyConnectorDomain": { + "message": "Key Connector 域名" }, "leaveOrganization": { "message": "退出组织" @@ -3532,12 +3590,36 @@ "deviceTrusted": { "message": "设备已信任" }, - "sendsNoItemsTitle": { - "message": "没有活跃的 Send", + "trustOrganization": { + "message": "信任组织" + }, + "trust": { + "message": "信任" + }, + "doNotTrust": { + "message": "不信任" + }, + "organizationNotTrusted": { + "message": "组织未被信任" + }, + "emergencyAccessTrustWarning": { + "message": "为了您的账户安全,确认前请先确认:您已授予该用户紧急访问权限,以及其指纹与其账户中显示的指纹相匹配" + }, + "orgTrustWarning": { + "message": "为了您的账户安全,继续前请先确认:您是启用了账户恢复功能的该组织的成员,以及下方显示的指纹与此组织的指纹相匹配。" + }, + "orgTrustWarning1": { + "message": "此组织有一个企业策略,将为您注册账户恢复。注册后将允许组织管理员更改您的主密码。继续前请先确认:您认识此组织,以及下方显示的指纹短语与此组织的指纹相匹配。" + }, + "trustUser": { + "message": "信任用户" + }, + "sendsTitleNoItems": { + "message": "安全地发送敏感信息", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, - "sendsNoItemsMessage": { - "message": "使用 Send 与任何人安全地分享加密信息。", + "sendsBodyNoItems": { + "message": "在任何平台上安全地与任何人共享文件和数据。您的信息将在限制曝光的同时保持端到端加密。", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "inputRequired": { @@ -4418,13 +4500,13 @@ "message": "项目历史记录" }, "lastEdited": { - "message": "上次编辑" + "message": "最后编辑于" }, "ownerYou": { "message": "所有者:您" }, "linked": { - "message": "链接型" + "message": "已链接" }, "copySuccessful": { "message": "复制成功" @@ -4436,7 +4518,7 @@ "message": "添加附件" }, "maxFileSizeSansPunctuation": { - "message": "最大文件大小为 500 MB" + "message": "文件最大为 500 MB" }, "deleteAttachmentName": { "message": "删除附件 $NAME$", @@ -4456,6 +4538,33 @@ } } }, + "downloadBitwarden": { + "message": "下载 Bitwarden" + }, + "downloadBitwardenOnAllDevices": { + "message": "在所有设备上下载 Bitwarden" + }, + "getTheMobileApp": { + "message": "获取移动 App" + }, + "getTheMobileAppDesc": { + "message": "使用 Bitwarden 移动 App,随时随地访问您的密码。" + }, + "getTheDesktopApp": { + "message": "获取桌面 App" + }, + "getTheDesktopAppDesc": { + "message": "无需使用浏览器访问您的密码库,然后在桌面 App 和浏览器扩展中同时设置生物识别解锁,即可实现快速解锁。" + }, + "downloadFromBitwardenNow": { + "message": "立即从 bitwarden.com 下载" + }, + "getItOnGooglePlay": { + "message": "从 Google Play 获取" + }, + "downloadOnTheAppStore": { + "message": "从 App Store 下载" + }, "permanentlyDeleteAttachmentConfirmation": { "message": "您确定要永久删除此附件吗?" }, @@ -4571,7 +4680,7 @@ "message": "过期的支付卡" }, "cardExpiredMessage": { - "message": "如果您的支付卡已续期,请更新该卡的信息。" + "message": "如果您的支付卡已续期,请更新该卡的信息" }, "cardDetails": { "message": "支付卡详细信息" @@ -4858,7 +4967,7 @@ "message": "自定义超时时间最小为 1 分钟。" }, "additionalContentAvailable": { - "message": "有更多内容可用" + "message": "更多内容可用" }, "fileSavedToDevice": { "message": "文件已保存到设备。可以在设备下载中进行管理。" @@ -4917,6 +5026,15 @@ "biometricsStatusHelptextUnavailableReasonUnknown": { "message": "由于某个未知的原因,生物识别解锁当前不可用。" }, + "unlockVault": { + "message": "数秒内解锁您的密码库" + }, + "unlockVaultDesc": { + "message": "您可以自定义解锁和超时设置,以便更快速地访问您的密码库。" + }, + "unlockPinSet": { + "message": "解锁 PIN 设置" + }, "authenticating": { "message": "正在验证" }, @@ -4928,8 +5046,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": { @@ -5079,42 +5197,6 @@ "beta": { "message": "Beta 版" }, - "importantNotice": { - "message": "重要通知" - }, - "setupTwoStepLogin": { - "message": "设置两步登录" - }, - "newDeviceVerificationNoticeContentPage1": { - "message": "从 2025 年 02 月起,当有来自新设备的登录时,Bitwarden 将向您的账户电子邮箱发送验证码。" - }, - "newDeviceVerificationNoticeContentPage2": { - "message": "您可以设置两步登录作为保护账户的替代方法,或将您的电子邮箱更改为您可以访问的电子邮箱。" - }, - "remindMeLater": { - "message": "稍后提醒我" - }, - "newDeviceVerificationNoticePageOneFormContent": { - "message": "您可以正常访问您的电子邮箱 $EMAIL$ 吗?", - "placeholders": { - "email": { - "content": "$1", - "example": "your_name@email.com" - } - } - }, - "newDeviceVerificationNoticePageOneEmailAccessNo": { - "message": "不,我不能" - }, - "newDeviceVerificationNoticePageOneEmailAccessYes": { - "message": "是的,我可以正常访问我的电子邮箱" - }, - "turnOnTwoStepLogin": { - "message": "启用两步登录" - }, - "changeAcctEmail": { - "message": "更改账户电子邮箱" - }, "extensionWidth": { "message": "扩展宽度" }, @@ -5152,7 +5234,7 @@ "message": "SSH 密钥导入成功" }, "cannotRemoveViewOnlyCollections": { - "message": "您无法删除仅具有「查看」权限的集合:$COLLECTIONS$", + "message": "您无法移除仅具有「查看」权限的集合:$COLLECTIONS$", "placeholders": { "collections": { "content": "$1", @@ -5169,6 +5251,12 @@ "changeAtRiskPassword": { "message": "更改有风险的密码" }, + "settingsVaultOptions": { + "message": "密码库选项" + }, + "emptyVaultDescription": { + "message": "密码库不仅保护您的密码。在这里还可以安全地存储登录、ID、支付卡和笔记。" + }, "introCarouselLabel": { "message": "欢迎使用 Bitwarden" }, @@ -5195,5 +5283,98 @@ }, "secureDevicesBody": { "message": "使用 Bitwarden 移动端、浏览器和桌面 App 在无限制的设备上保存无限数量的密码。" + }, + "nudgeBadgeAria": { + "message": "1 个通知" + }, + "emptyVaultNudgeTitle": { + "message": "导入现有密码" + }, + "emptyVaultNudgeBody": { + "message": "使用导入器快速将登录传输到 Bitwarden 而无需手动添加。" + }, + "emptyVaultNudgeButton": { + "message": "立即导入" + }, + "hasItemsVaultNudgeTitle": { + "message": "欢迎来到您的密码库!" + }, + "hasItemsVaultNudgeBodyOne": { + "message": "为当前页面自动填充项目" + }, + "hasItemsVaultNudgeBodyTwo": { + "message": "收藏项目以便轻松访问" + }, + "hasItemsVaultNudgeBodyThree": { + "message": "在密码库中搜索其他内容" + }, + "newLoginNudgeTitle": { + "message": "使用自动填充节省时间" + }, + "newLoginNudgeBodyOne": { + "message": "包含", + "description": "This is in multiple parts to allow for bold text in the middle of the sentence.", + "example": "Include a Website so this login appears as an autofill suggestion." + }, + "newLoginNudgeBodyBold": { + "message": "网站", + "description": "This is in multiple parts to allow for bold text in the middle of the sentence.", + "example": "Include a Website so this login appears as an autofill suggestion." + }, + "newLoginNudgeBodyTwo": { + "message": "以便将此登录显示为自动填充建议。", + "description": "This is in multiple parts to allow for bold text in the middle of the sentence.", + "example": "Include a Website so this login appears as an autofill suggestion." + }, + "newCardNudgeTitle": { + "message": "无缝在线结账" + }, + "newCardNudgeBody": { + "message": "使用支付卡,安全准确地轻松自动填充付款表单。" + }, + "newIdentityNudgeTitle": { + "message": "简化账户的创建" + }, + "newIdentityNudgeBody": { + "message": "使用身份,快速自动填充冗长的注册或联系表单。" + }, + "newNoteNudgeTitle": { + "message": "保持您的敏感数据的安全" + }, + "newNoteNudgeBody": { + "message": "使用笔记,安全地存储敏感数据,例如银行或保险详细信息。" + }, + "newSshNudgeTitle": { + "message": "开发人员友好的 SSH 访问权限" + }, + "newSshNudgeBodyOne": { + "message": "存储您的密钥并与 SSH 代理连接,以进行快速、加密的身份验证。", + "description": "Two part message", + "example": "Store your keys and connect with the SSH agent for fast, encrypted authentication. Learn more about SSH agent" + }, + "newSshNudgeBodyTwo": { + "message": "进一步了解 SSH 代理", + "description": "Two part message", + "example": "Store your keys and connect with the SSH agent for fast, encrypted authentication. Learn more about SSH agent" + }, + "generatorNudgeTitle": { + "message": "快速创建密码" + }, + "generatorNudgeBodyOne": { + "message": "一键创建强大且唯一的密码", + "description": "Two part message", + "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." + }, + "generatorNudgeBodyTwo": { + "message": "帮助您保持登录安全。", + "description": "Two part message", + "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." + }, + "generatorNudgeBodyAria": { + "message": "点击「生成密码」按钮,轻松创建强大且唯一的密码,帮助您保持登录安全。", + "description": "Aria label for the body content of the generator nudge" + }, + "noPermissionsViewPage": { + "message": "您没有查看此页面的权限。请尝试使用其他账户登录。" } } diff --git a/apps/browser/src/_locales/zh_TW/messages.json b/apps/browser/src/_locales/zh_TW/messages.json index 7d56ecd8e63..00fb93c6302 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)" @@ -186,7 +189,7 @@ "message": "複製備註" }, "copy": { - "message": "Copy", + "message": "複製", "description": "Copy to clipboard" }, "fill": { @@ -538,7 +541,7 @@ "description": "Label for the avoid ambiguous characters checkbox." }, "generatorPolicyInEffect": { - "message": "企業原則之要求已在你的產生器選項中生效", + "message": "企業原則之要求已在您的產生器選項中生效。", "description": "Indicates that a policy limits the credential generator screen." }, "searchVault": { @@ -796,7 +799,7 @@ "message": "您已成功建立新帳號!" }, "youHaveBeenLoggedIn": { - "message": "你已經登入!" + "message": "您已經登入!" }, "youSuccessfullyLoggedIn": { "message": "登入成功" @@ -857,7 +860,7 @@ "message": "已登出" }, "loggedOutDesc": { - "message": "你已經登出了你的帳號。" + "message": "您已經登出了您的帳號。" }, "loginExpired": { "message": "您的登入階段已過期。" @@ -869,7 +872,7 @@ "message": "登入 Bitwarden" }, "enterTheCodeSentToYourEmail": { - "message": "Enter the code sent to your email" + "message": "輸入傳送到您電子郵件信箱的驗證碼" }, "enterTheCodeFromYourAuthenticatorApp": { "message": "Enter the code from your authenticator app" @@ -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,55 @@ "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.", + "notificationNewItemAria": { + "message": "New Item, opens in new window", + "description": "Aria label for the new item button in notification bar confirmation message when error is prompted" + }, + "notificationEditTooltip": { + "message": "Edit before saving", + "description": "Tooltip and Aria label for edit button on cipher item" + }, + "newNotification": { + "message": "新通知" + }, + "labelWithNotification": { + "message": "$LABEL$: New notification", + "description": "Label for the notification with a new login suggestion.", "placeholders": { - "username": { - "content": "$1" + "label": { + "content": "$1", + "example": "Login" } - }, - "description": "Shown to user after login is updated." + } + }, + "notificationLoginSaveConfirmation": { + "message": "saved to Bitwarden.", + "description": "Shown to user after item is saved." + }, + "notificationLoginUpdatedConfirmation": { + "message": "updated in Bitwarden.", + "description": "Shown to user after item is updated." + }, + "selectItemAriaLabel": { + "message": "Select $ITEMTYPE$, $ITEMNAME$", + "description": "Used by screen readers. $1 is the item type (like vault or folder), $2 is the selected item name.", + "placeholders": { + "itemType": { + "content": "$1" + }, + "itemName": { + "content": "$2" + } + } }, "saveAsNewLoginAction": { "message": "Save as new login", @@ -1082,12 +1120,16 @@ "message": "Update login", "description": "Button text for updating an existing login entry." }, - "saveLoginPrompt": { - "message": "Save login?", + "unlockToSave": { + "message": "Unlock to save this login", + "description": "User prompt to take action in order to save the login they just entered." + }, + "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": { @@ -1323,8 +1365,8 @@ "featureUnavailable": { "message": "功能不可用" }, - "encryptionKeyMigrationRequired": { - "message": "需要遷移加密金鑰。請透過網頁版密碼庫登入以更新您的加密金鑰。" + "legacyEncryptionUnsupported": { + "message": "Legacy encryption is no longer supported. Please contact support to recover your account." }, "premiumMembership": { "message": "進階會員" @@ -1445,13 +1487,13 @@ "message": "將您的安全鑰匙插入電腦的 USB 連接埠,然後觸摸其按鈕(如有的話)。" }, "openInNewTab": { - "message": "Open in new tab" + "message": "在新分頁中開啟" }, "webAuthnAuthenticate": { "message": "驗證 WebAuthn" }, "readSecurityKey": { - "message": "Read security key" + "message": "讀取安全金鑰" }, "awaitingSecurityKeyInteraction": { "message": "Awaiting security key interaction..." @@ -1469,7 +1511,7 @@ "message": "兩步驟登入選項" }, "selectTwoStepLoginMethod": { - "message": "Select two-step login method" + "message": "選擇兩步驟登入方法" }, "recoveryCodeDesc": { "message": "無法使用任何雙因素提供程式嗎?請使用您的復原碼以停用您帳戶的所有雙因素提供程式。" @@ -1557,6 +1599,24 @@ "autofillSuggestionsSectionTitle": { "message": "自動填入建議" }, + "autofillSpotlightTitle": { + "message": "Easily find autofill suggestions" + }, + "autofillSpotlightDesc": { + "message": "Turn off your browser's autofill settings, so they don't conflict with Bitwarden." + }, + "turnOffBrowserAutofill": { + "message": "Turn off $BROWSER$ autofill", + "placeholders": { + "browser": { + "content": "$1", + "example": "Chrome" + } + } + }, + "turnOffAutofill": { + "message": "Turn off autofill" + }, "showInlineMenuLabel": { "message": "在表單欄位上顯示自動填入選單" }, @@ -1573,7 +1633,7 @@ "message": "適用於所有已登入的帳戶。" }, "turnOffBrowserBuiltInPasswordManagerSettings": { - "message": "關閉你的瀏覽器內建密碼管理器設定以避免衝突。" + "message": "關閉您的瀏覽器內建密碼管理器設定以避免衝突。" }, "turnOffBrowserBuiltInPasswordManagerSettingsLink": { "message": "編輯瀏覽器設定" @@ -1663,7 +1723,7 @@ "message": "透過拖曳來排序" }, "dragToReorder": { - "message": "Drag to reorder" + "message": "拖曳以重新排序" }, "cfTypeText": { "message": "文字型" @@ -2144,6 +2204,9 @@ "useThisPassword": { "message": "使用此密碼" }, + "useThisPassphrase": { + "message": "Use this passphrase" + }, "useThisUsername": { "message": "使用此使用者名稱" }, @@ -2167,15 +2230,6 @@ "vaultTimeoutAction1": { "message": "逾時後動作" }, - "newCustomizationOptionsCalloutTitle": { - "message": "New customization options" - }, - "newCustomizationOptionsCalloutContent": { - "message": "Customize your vault experience with quick copy actions, compact mode, and more!" - }, - "newCustomizationOptionsCalloutLink": { - "message": "View all Appearance settings" - }, "lock": { "message": "鎖定", "description": "Verb form: to make secure or inaccessible by" @@ -2623,6 +2677,10 @@ "message": "所有 Send", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, + "maxAccessCountReached": { + "message": "Max access count reached", + "description": "This text will be displayed after a Send has been accessed the maximum amount of times." + }, "hideTextByDefault": { "message": "默認隱藏文字" }, @@ -2963,14 +3021,14 @@ "copyCustomFieldNameNotUnique": { "message": "找不到唯一識別碼。" }, - "convertOrganizationEncryptionDesc": { - "message": "$ORGANIZATION$ 使用自我裝載金鑰伺服器 SSO。此組織的成員登入時將不再需要主密碼。", - "placeholders": { - "organization": { - "content": "$1", - "example": "My Org Name" - } - } + "removeMasterPasswordForOrganizationUserKeyConnector": { + "message": "A master password is no longer required for members of the following organization. Please confirm the domain below with your organization administrator." + }, + "organizationName": { + "message": "Organization name" + }, + "keyConnectorDomain": { + "message": "Key Connector domain" }, "leaveOrganization": { "message": "離開組織" @@ -3532,12 +3590,36 @@ "deviceTrusted": { "message": "裝置已信任" }, - "sendsNoItemsTitle": { - "message": "沒有可用的 Send", + "trustOrganization": { + "message": "Trust organization" + }, + "trust": { + "message": "Trust" + }, + "doNotTrust": { + "message": "Do not trust" + }, + "organizationNotTrusted": { + "message": "Organization is not trusted" + }, + "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" + }, + "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." + }, + "orgTrustWarning1": { + "message": "This organization has an Enterprise policy that will enroll you in account recovery. Enrollment will allow organization administrators to change your password. Only proceed if you recognize this organization and the fingerprint phrase displayed below matches the organization's fingerprint." + }, + "trustUser": { + "message": "Trust user" + }, + "sendsTitleNoItems": { + "message": "Send sensitive information safely", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, - "sendsNoItemsMessage": { - "message": "使用 Send 可以與任何人安全地共用加密資訊。", + "sendsBodyNoItems": { + "message": "Share files and data securely with anyone, on any platform. Your information will remain end-to-end encrypted while limiting exposure.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "inputRequired": { @@ -4456,6 +4538,33 @@ } } }, + "downloadBitwarden": { + "message": "Download Bitwarden" + }, + "downloadBitwardenOnAllDevices": { + "message": "Download Bitwarden on all devices" + }, + "getTheMobileApp": { + "message": "Get the mobile app" + }, + "getTheMobileAppDesc": { + "message": "Access your passwords on the go with the Bitwarden mobile app." + }, + "getTheDesktopApp": { + "message": "Get the desktop app" + }, + "getTheDesktopAppDesc": { + "message": "Access your vault without a browser, then set up unlock with biometrics to expedite unlocking in both the desktop app and browser extension." + }, + "downloadFromBitwardenNow": { + "message": "Download from bitwarden.com now" + }, + "getItOnGooglePlay": { + "message": "Get it on Google Play" + }, + "downloadOnTheAppStore": { + "message": "Download on the App Store" + }, "permanentlyDeleteAttachmentConfirmation": { "message": "您確定要永久刪除此附檔嗎?" }, @@ -4917,6 +5026,15 @@ "biometricsStatusHelptextUnavailableReasonUnknown": { "message": "基於不明原因,生物辨識解鎖無法使用。" }, + "unlockVault": { + "message": "Unlock your vault in seconds" + }, + "unlockVaultDesc": { + "message": "You can customize your unlock and timeout settings to more quickly access your vault." + }, + "unlockPinSet": { + "message": "Unlock PIN set" + }, "authenticating": { "message": "驗證中" }, @@ -4928,8 +5046,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": { @@ -5079,42 +5197,6 @@ "beta": { "message": "測試版" }, - "importantNotice": { - "message": "重要通知" - }, - "setupTwoStepLogin": { - "message": "啟動兩階段登入" - }, - "newDeviceVerificationNoticeContentPage1": { - "message": "從 2025 年 2 月開始,Bitwarden 會傳送代碼到您的帳號電子郵件中來驗證新裝置的登入。" - }, - "newDeviceVerificationNoticeContentPage2": { - "message": "您可以啟動兩階段認證來保護您的帳號或更改您可以存取的電子郵件位址。" - }, - "remindMeLater": { - "message": "稍後再提醒我" - }, - "newDeviceVerificationNoticePageOneFormContent": { - "message": "您可以存取您的電子郵件位址 $EMAIL$ 嗎?", - "placeholders": { - "email": { - "content": "$1", - "example": "your_name@email.com" - } - } - }, - "newDeviceVerificationNoticePageOneEmailAccessNo": { - "message": "不,我不行" - }, - "newDeviceVerificationNoticePageOneEmailAccessYes": { - "message": "是,我可以存取我的電子郵件位址" - }, - "turnOnTwoStepLogin": { - "message": "啟動兩階段登入" - }, - "changeAcctEmail": { - "message": "更改帳號電子郵件位址" - }, "extensionWidth": { "message": "擴充套件寬度" }, @@ -5169,6 +5251,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 +5283,98 @@ }, "secureDevicesBody": { "message": "Save unlimited passwords across unlimited devices with Bitwarden mobile, browser, and desktop apps." + }, + "nudgeBadgeAria": { + "message": "1 notification" + }, + "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!" + }, + "hasItemsVaultNudgeBodyOne": { + "message": "Autofill items for the current page" + }, + "hasItemsVaultNudgeBodyTwo": { + "message": "Favorite items for easy access" + }, + "hasItemsVaultNudgeBodyThree": { + "message": "Search your vault for something else" + }, + "newLoginNudgeTitle": { + "message": "Save time with autofill" + }, + "newLoginNudgeBodyOne": { + "message": "Include a", + "description": "This is in multiple parts to allow for bold text in the middle of the sentence.", + "example": "Include a Website so this login appears as an autofill suggestion." + }, + "newLoginNudgeBodyBold": { + "message": "Website", + "description": "This is in multiple parts to allow for bold text in the middle of the sentence.", + "example": "Include a Website so this login appears as an autofill suggestion." + }, + "newLoginNudgeBodyTwo": { + "message": "so this login appears as an autofill suggestion.", + "description": "This is in multiple parts to allow for bold text in the middle of the sentence.", + "example": "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" + }, + "newSshNudgeBodyOne": { + "message": "Store your keys and connect with the SSH agent for fast, encrypted authentication.", + "description": "Two part message", + "example": "Store your keys and connect with the SSH agent for fast, encrypted authentication. Learn more about SSH agent" + }, + "newSshNudgeBodyTwo": { + "message": "Learn more about SSH agent", + "description": "Two part message", + "example": "Store your keys and connect with the SSH agent for fast, encrypted authentication. Learn more about SSH agent" + }, + "generatorNudgeTitle": { + "message": "Quickly create passwords" + }, + "generatorNudgeBodyOne": { + "message": "Easily create strong and unique passwords by clicking on", + "description": "Two part message", + "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." + }, + "generatorNudgeBodyTwo": { + "message": "to help you keep your logins secure.", + "description": "Two part message", + "example": "Easily create strong and unique passwords by clicking on {icon} to help you keep your logins secure." + }, + "generatorNudgeBodyAria": { + "message": "Easily create strong and unique passwords by clicking on the Generate password button to help you keep your logins secure.", + "description": "Aria label for the body content of the generator nudge" + }, + "noPermissionsViewPage": { + "message": "You do not have permissions to view this page. Try logging in with a different account." } } diff --git a/apps/browser/src/auth/popup/account-switching/account-switcher.component.html b/apps/browser/src/auth/popup/account-switching/account-switcher.component.html index de8ab4c7b08..b9f9b984c69 100644 --- a/apps/browser/src/auth/popup/account-switching/account-switcher.component.html +++ b/apps/browser/src/auth/popup/account-switching/account-switcher.component.html @@ -57,19 +57,19 @@ [disabled]="currentAccount.status === lockedStatus || !activeUserCanLock" [title]="!activeUserCanLock ? ('unlockMethodNeeded' | i18n) : ''" > - + {{ "lockNow" | i18n }} diff --git a/apps/browser/src/auth/popup/account-switching/account-switcher.component.ts b/apps/browser/src/auth/popup/account-switching/account-switcher.component.ts index 78bee121afb..3c94fbeef70 100644 --- a/apps/browser/src/auth/popup/account-switching/account-switcher.component.ts +++ b/apps/browser/src/auth/popup/account-switching/account-switcher.component.ts @@ -34,7 +34,6 @@ import { CurrentAccountComponent } from "./current-account.component"; import { AccountSwitcherService } from "./services/account-switcher.service"; @Component({ - standalone: true, templateUrl: "account-switcher.component.html", imports: [ CommonModule, diff --git a/apps/browser/src/auth/popup/account-switching/account.component.html b/apps/browser/src/auth/popup/account-switching/account.component.html index d2e15d31899..d22ce9c9366 100644 --- a/apps/browser/src/auth/popup/account-switching/account.component.html +++ b/apps/browser/src/auth/popup/account-switching/account.component.html @@ -32,13 +32,13 @@ - + diff --git a/apps/browser/src/auth/popup/account-switching/account.component.ts b/apps/browser/src/auth/popup/account-switching/account.component.ts index dad74977d34..cdd2656fdc1 100644 --- a/apps/browser/src/auth/popup/account-switching/account.component.ts +++ b/apps/browser/src/auth/popup/account-switching/account.component.ts @@ -13,7 +13,6 @@ import { BiometricsService } from "@bitwarden/key-management"; import { AccountSwitcherService, AvailableAccount } from "./services/account-switcher.service"; @Component({ - standalone: true, selector: "auth-account", templateUrl: "account.component.html", imports: [CommonModule, JslibModule, AvatarModule, ItemModule], diff --git a/apps/browser/src/auth/popup/account-switching/current-account.component.ts b/apps/browser/src/auth/popup/account-switching/current-account.component.ts index ea41a627848..63e8481621a 100644 --- a/apps/browser/src/auth/popup/account-switching/current-account.component.ts +++ b/apps/browser/src/auth/popup/account-switching/current-account.component.ts @@ -24,7 +24,6 @@ export type CurrentAccount = { @Component({ selector: "app-current-account", templateUrl: "current-account.component.html", - standalone: true, imports: [CommonModule, JslibModule, AvatarModule, RouterModule], }) export class CurrentAccountComponent { diff --git a/apps/browser/src/auth/popup/components/set-pin.component.html b/apps/browser/src/auth/popup/components/set-pin.component.html index 58cb42456ee..d525f9378f1 100644 --- a/apps/browser/src/auth/popup/components/set-pin.component.html +++ b/apps/browser/src/auth/popup/components/set-pin.component.html @@ -5,7 +5,7 @@

- {{ "setYourPinCode1" | i18n }} + {{ "setPinCode" | i18n }}

{{ "pin" | i18n }} diff --git a/apps/browser/src/auth/popup/components/set-pin.component.ts b/apps/browser/src/auth/popup/components/set-pin.component.ts index d79f9eeca89..a9e8e1b122f 100644 --- a/apps/browser/src/auth/popup/components/set-pin.component.ts +++ b/apps/browser/src/auth/popup/components/set-pin.component.ts @@ -14,7 +14,6 @@ import { } from "@bitwarden/components"; @Component({ - standalone: true, templateUrl: "set-pin.component.html", imports: [ DialogModule, diff --git a/apps/browser/src/auth/popup/extension-anon-layout-wrapper/extension-anon-layout-wrapper.component.html b/apps/browser/src/auth/popup/extension-anon-layout-wrapper/extension-anon-layout-wrapper.component.html index 54cb5203a87..bd2886dacf0 100644 --- a/apps/browser/src/auth/popup/extension-anon-layout-wrapper/extension-anon-layout-wrapper.component.html +++ b/apps/browser/src/auth/popup/extension-anon-layout-wrapper/extension-anon-layout-wrapper.component.html @@ -5,7 +5,12 @@ [showBackButton]="showBackButton" [pageTitle]="''" > - + diff --git a/apps/browser/src/auth/popup/extension-anon-layout-wrapper/extension-anon-layout-wrapper.component.ts b/apps/browser/src/auth/popup/extension-anon-layout-wrapper/extension-anon-layout-wrapper.component.ts index 51dbb6503d7..b335155d355 100644 --- a/apps/browser/src/auth/popup/extension-anon-layout-wrapper/extension-anon-layout-wrapper.component.ts +++ b/apps/browser/src/auth/popup/extension-anon-layout-wrapper/extension-anon-layout-wrapper.component.ts @@ -12,6 +12,7 @@ import { } from "@bitwarden/auth/angular"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { Icon, IconModule, Translation } from "@bitwarden/components"; +import { I18nPipe } from "@bitwarden/ui-common"; import { PopOutComponent } from "../../../platform/popup/components/pop-out.component"; import { PopupHeaderComponent } from "../../../platform/popup/layout/popup-header.component"; @@ -30,12 +31,12 @@ export interface ExtensionAnonLayoutWrapperData extends AnonLayoutWrapperData { } @Component({ - standalone: true, templateUrl: "extension-anon-layout-wrapper.component.html", imports: [ AnonLayoutComponent, CommonModule, CurrentAccountComponent, + I18nPipe, IconModule, PopOutComponent, PopupPageComponent, diff --git a/apps/browser/src/auth/popup/extension-anon-layout-wrapper/extension-anon-layout-wrapper.stories.ts b/apps/browser/src/auth/popup/extension-anon-layout-wrapper/extension-anon-layout-wrapper.stories.ts index a0990485d49..78ca577a69d 100644 --- a/apps/browser/src/auth/popup/extension-anon-layout-wrapper/extension-anon-layout-wrapper.stories.ts +++ b/apps/browser/src/auth/popup/extension-anon-layout-wrapper/extension-anon-layout-wrapper.stories.ts @@ -168,18 +168,21 @@ type Story = StoryObj; @Component({ selector: "bit-default-primary-outlet-example-component", template: "

Primary Outlet Example:
your primary component goes here

", + standalone: false, }) class DefaultPrimaryOutletExampleComponent {} @Component({ selector: "bit-default-secondary-outlet-example-component", template: "

Secondary Outlet Example:
your secondary component goes here

", + standalone: false, }) class DefaultSecondaryOutletExampleComponent {} @Component({ selector: "bit-default-env-selector-outlet-example-component", template: "

Env Selector Outlet Example:
your env selector component goes here

", + standalone: false, }) class DefaultEnvSelectorOutletExampleComponent {} @@ -264,6 +267,7 @@ const changedData: ExtensionAnonLayoutWrapperData = { template: ` `, + standalone: false, }) export class DynamicContentExampleComponent { initialData = true; diff --git a/apps/browser/src/auth/popup/login-decryption-options/extension-login-decryption-options.service.ts b/apps/browser/src/auth/popup/login-decryption-options/extension-login-decryption-options.service.ts index ea529e277e6..3e591e08ac1 100644 --- a/apps/browser/src/auth/popup/login-decryption-options/extension-login-decryption-options.service.ts +++ b/apps/browser/src/auth/popup/login-decryption-options/extension-login-decryption-options.service.ts @@ -24,6 +24,8 @@ export class ExtensionLoginDecryptionOptionsService // start listening for "switchAccountFinish" or "doneLoggingOut" const messagePromise = firstValueFrom(postLogoutMessageListener$); + // FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling. + // eslint-disable-next-line @typescript-eslint/no-floating-promises super.logOut(); // wait for messages diff --git a/apps/browser/src/auth/popup/set-password.component.ts b/apps/browser/src/auth/popup/set-password.component.ts index accde2e9a09..2a796854531 100644 --- a/apps/browser/src/auth/popup/set-password.component.ts +++ b/apps/browser/src/auth/popup/set-password.component.ts @@ -5,5 +5,6 @@ import { SetPasswordComponent as BaseSetPasswordComponent } from "@bitwarden/ang @Component({ selector: "app-set-password", templateUrl: "set-password.component.html", + standalone: false, }) export class SetPasswordComponent extends BaseSetPasswordComponent {} 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..d835497d9be 100644 --- a/apps/browser/src/auth/popup/settings/account-security.component.html +++ b/apps/browser/src/auth/popup/settings/account-security.component.html @@ -5,6 +5,13 @@
+
@@ -23,7 +30,7 @@ `, }) @@ -49,6 +58,10 @@ describe("AccountSecurityComponent", () => { const biometricStateService = mock(); const policyService = mock(); const pinServiceAbstraction = mock(); + const keyService = mock(); + const validationService = mock(); + const dialogService = mock(); + const platformUtilsService = mock(); beforeEach(async () => { await TestBed.configureTestingModule({ @@ -57,13 +70,13 @@ describe("AccountSecurityComponent", () => { { provide: AccountSecurityComponent, useValue: mock() }, { provide: BiometricsService, useValue: mock() }, { provide: BiometricStateService, useValue: biometricStateService }, - { provide: DialogService, useValue: mock() }, + { provide: DialogService, useValue: dialogService }, { provide: EnvironmentService, useValue: mock() }, { provide: I18nService, useValue: mock() }, { provide: MessageSender, useValue: mock() }, - { provide: KeyService, useValue: mock() }, + { provide: KeyService, useValue: keyService }, { provide: PinServiceAbstraction, useValue: pinServiceAbstraction }, - { provide: PlatformUtilsService, useValue: mock() }, + { provide: PlatformUtilsService, useValue: platformUtilsService }, { provide: PolicyService, useValue: policyService }, { provide: PopupRouterCacheService, useValue: mock() }, { provide: StateService, useValue: mock() }, @@ -71,14 +84,24 @@ describe("AccountSecurityComponent", () => { { provide: UserVerificationService, useValue: mock() }, { provide: VaultTimeoutService, useValue: mock() }, { provide: VaultTimeoutSettingsService, useValue: vaultTimeoutSettingsService }, + { provide: StateProvider, useValue: mock() }, + { provide: CipherService, useValue: mock() }, + { provide: ApiService, useValue: mock() }, + { provide: LogService, useValue: mock() }, + { provide: OrganizationService, useValue: mock() }, + { provide: CollectionService, useValue: mock() }, + { provide: ConfigService, useValue: mock() }, + { provide: ValidationService, useValue: validationService }, ], }) .overrideComponent(AccountSecurityComponent, { remove: { imports: [PopOutComponent], + providers: [DialogService], }, add: { imports: [MockPopOutComponent], + providers: [{ provide: DialogService, useValue: dialogService }], }, }) .compileComponents(); @@ -93,10 +116,17 @@ describe("AccountSecurityComponent", () => { vaultTimeoutSettingsService.getVaultTimeoutActionByUserId$.mockReturnValue( of(VaultTimeoutAction.Lock), ); + vaultTimeoutSettingsService.getVaultTimeoutActionByUserId$.mockReturnValue( + of(VaultTimeoutAction.Lock), + ); biometricStateService.promptAutomatically$ = of(false); pinServiceAbstraction.isPinSet.mockResolvedValue(false); }); + afterEach(() => { + jest.resetAllMocks(); + }); + it("pin enabled when RemoveUnlockWithPin policy is not set", async () => { // @ts-strict-ignore policyService.policiesByType$.mockReturnValue(of([null])); @@ -198,4 +228,136 @@ describe("AccountSecurityComponent", () => { const pinInputElement = fixture.debugElement.query(By.css("#pin")); expect(pinInputElement).toBeNull(); }); + + describe("updateBiometric", () => { + let browserApiSpy: jest.SpyInstance; + + beforeEach(() => { + policyService.policiesByType$.mockReturnValue(of([null])); + browserApiSpy = jest.spyOn(BrowserApi, "requestPermission"); + browserApiSpy.mockResolvedValue(true); + }); + + describe("updating to false", () => { + it("calls biometricStateService methods with false when false", async () => { + await component.ngOnInit(); + await component.updateBiometric(false); + + expect(biometricStateService.setBiometricUnlockEnabled).toHaveBeenCalledWith(false); + expect(biometricStateService.setFingerprintValidated).toHaveBeenCalledWith(false); + }); + }); + + describe("updating to true", () => { + let trySetupBiometricsSpy: jest.SpyInstance; + + beforeEach(() => { + trySetupBiometricsSpy = jest.spyOn(component, "trySetupBiometrics"); + }); + + it("displays permission error dialog when nativeMessaging permission is not granted", async () => { + browserApiSpy.mockResolvedValue(false); + + await component.ngOnInit(); + await component.updateBiometric(true); + + expect(dialogService.openSimpleDialog).toHaveBeenCalledWith({ + title: { key: "nativeMessaginPermissionErrorTitle" }, + content: { key: "nativeMessaginPermissionErrorDesc" }, + acceptButtonText: { key: "ok" }, + cancelButtonText: null, + type: "danger", + }); + expect(component.form.controls.biometric.value).toBe(false); + expect(trySetupBiometricsSpy).not.toHaveBeenCalled(); + }); + + it("displays a specific sidebar dialog when nativeMessaging permissions throws an error on firefox + sidebar", async () => { + browserApiSpy.mockRejectedValue(new Error("Permission denied")); + platformUtilsService.isFirefox.mockReturnValue(true); + jest.spyOn(BrowserPopupUtils, "inSidebar").mockReturnValue(true); + + await component.ngOnInit(); + await component.updateBiometric(true); + + expect(dialogService.openSimpleDialog).toHaveBeenCalledWith({ + title: { key: "nativeMessaginPermissionSidebarTitle" }, + content: { key: "nativeMessaginPermissionSidebarDesc" }, + acceptButtonText: { key: "ok" }, + cancelButtonText: null, + type: "info", + }); + expect(component.form.controls.biometric.value).toBe(false); + expect(trySetupBiometricsSpy).not.toHaveBeenCalled(); + }); + + test.each([ + [false, false], + [false, true], + [true, false], + ])( + "displays a generic dialog when nativeMessaging permissions throws an error and isFirefox is %s and onSidebar is %s", + async (isFirefox, inSidebar) => { + browserApiSpy.mockRejectedValue(new Error("Permission denied")); + platformUtilsService.isFirefox.mockReturnValue(isFirefox); + jest.spyOn(BrowserPopupUtils, "inSidebar").mockReturnValue(inSidebar); + + await component.ngOnInit(); + await component.updateBiometric(true); + + expect(dialogService.openSimpleDialog).toHaveBeenCalledWith({ + title: { key: "nativeMessaginPermissionErrorTitle" }, + content: { key: "nativeMessaginPermissionErrorDesc" }, + acceptButtonText: { key: "ok" }, + cancelButtonText: null, + type: "danger", + }); + expect(component.form.controls.biometric.value).toBe(false); + expect(trySetupBiometricsSpy).not.toHaveBeenCalled(); + }, + ); + + it("refreshes additional keys and attempts to setup biometrics when enabled with nativeMessaging permission", async () => { + const setupBiometricsResult = true; + trySetupBiometricsSpy.mockResolvedValue(setupBiometricsResult); + + await component.ngOnInit(); + await component.updateBiometric(true); + + expect(keyService.refreshAdditionalKeys).toHaveBeenCalledWith(mockUserId); + expect(biometricStateService.setBiometricUnlockEnabled).toHaveBeenCalledWith( + setupBiometricsResult, + ); + expect(component.form.controls.biometric.value).toBe(setupBiometricsResult); + }); + + it("handles failed biometrics setup", async () => { + const setupBiometricsResult = false; + trySetupBiometricsSpy.mockResolvedValue(setupBiometricsResult); + + await component.ngOnInit(); + await component.updateBiometric(true); + + expect(biometricStateService.setBiometricUnlockEnabled).toHaveBeenCalledWith( + setupBiometricsResult, + ); + expect(biometricStateService.setFingerprintValidated).toHaveBeenCalledWith( + setupBiometricsResult, + ); + expect(component.form.controls.biometric.value).toBe(setupBiometricsResult); + }); + + it("handles error during biometrics setup", async () => { + // Simulate an error during biometrics setup + keyService.refreshAdditionalKeys.mockRejectedValue(new Error("UserId is required")); + + await component.ngOnInit(); + await component.updateBiometric(true); + + expect(validationService.showError).toHaveBeenCalledWith(new Error("UserId is required")); + expect(component.form.controls.biometric.value).toBe(false); + expect(trySetupBiometricsSpy).not.toHaveBeenCalled(); + }); + }); + }); }); diff --git a/apps/browser/src/auth/popup/settings/account-security.component.ts b/apps/browser/src/auth/popup/settings/account-security.component.ts index 32b32dc6022..1fc4650b6f5 100644 --- a/apps/browser/src/auth/popup/settings/account-security.component.ts +++ b/apps/browser/src/auth/popup/settings/account-security.component.ts @@ -22,6 +22,8 @@ import { } from "rxjs"; import { JslibModule } from "@bitwarden/angular/jslib.module"; +import { NudgesService, NudgeType } from "@bitwarden/angular/vault"; +import { SpotlightComponent } from "@bitwarden/angular/vault/components/spotlight/spotlight.component"; import { FingerprintDialogComponent, VaultTimeoutInputComponent } from "@bitwarden/auth/angular"; import { PinServiceAbstraction } from "@bitwarden/auth/common"; import { PolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction"; @@ -30,7 +32,6 @@ import { getFirstPolicy } from "@bitwarden/common/admin-console/services/policy/ import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; import { UserVerificationService } from "@bitwarden/common/auth/abstractions/user-verification/user-verification.service.abstraction"; import { getUserId } from "@bitwarden/common/auth/services/account.service"; -import { DeviceType } from "@bitwarden/common/enums"; import { VaultTimeout, VaultTimeoutAction, @@ -44,6 +45,7 @@ import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.servic import { MessagingService } from "@bitwarden/common/platform/abstractions/messaging.service"; import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; import { StateService } from "@bitwarden/common/platform/abstractions/state.service"; +import { ValidationService } from "@bitwarden/common/platform/abstractions/validation.service"; import { DialogRef, CardComponent, @@ -78,7 +80,6 @@ import { AwaitDesktopDialogComponent } from "./await-desktop-dialog.component"; @Component({ templateUrl: "account-security.component.html", - standalone: true, imports: [ CardComponent, CheckboxModule, @@ -97,6 +98,7 @@ import { AwaitDesktopDialogComponent } from "./await-desktop-dialog.component"; SectionComponent, SectionHeaderComponent, SelectModule, + SpotlightComponent, TypographyModule, VaultTimeoutInputComponent, ], @@ -110,7 +112,6 @@ export class AccountSecurityComponent implements OnInit, OnDestroy { hasVaultTimeoutPolicy = false; biometricUnavailabilityReason: string; showChangeMasterPass = true; - showAutoPrompt = true; pinEnabled$: Observable = of(true); form = this.formBuilder.group({ @@ -122,6 +123,14 @@ export class AccountSecurityComponent implements OnInit, OnDestroy { enableAutoBiometricsPrompt: true, }); + protected showAccountSecurityNudge$: Observable = + this.accountService.activeAccount$.pipe( + getUserId, + switchMap((userId) => + this.vaultNudgesService.showNudgeSpotlight$(NudgeType.AccountSecurity, userId), + ), + ); + private refreshTimeoutSettings$ = new BehaviorSubject(undefined); private destroy$ = new Subject(); @@ -144,14 +153,11 @@ export class AccountSecurityComponent implements OnInit, OnDestroy { private biometricStateService: BiometricStateService, private toastService: ToastService, private biometricsService: BiometricsService, + private vaultNudgesService: NudgesService, + private validationService: ValidationService, ) {} 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( @@ -409,6 +415,14 @@ export class AccountSecurityComponent implements OnInit, OnDestroy { } } + protected async dismissAccountSecurityNudge() { + const activeAccount = await firstValueFrom(this.accountService.activeAccount$); + if (!activeAccount) { + return; + } + await this.vaultNudgesService.dismissNudge(NudgeType.AccountSecurity, activeAccount.id); + } + async saveVaultTimeoutAction(value: VaultTimeoutAction) { if (value === VaultTimeoutAction.LogOut) { const confirmed = await this.dialogService.openSimpleDialog({ @@ -460,8 +474,17 @@ export class AccountSecurityComponent implements OnInit, OnDestroy { this.form.controls.pin.setValue(userHasPinSet, { emitEvent: false }); const requireReprompt = (await this.pinService.getPinLockType(userId)) == "EPHEMERAL"; this.form.controls.pinLockWithMasterPassword.setValue(requireReprompt, { emitEvent: false }); + if (userHasPinSet) { + this.toastService.showToast({ + variant: "success", + title: null, + message: this.i18nService.t("unlockPinSet"), + }); + await this.vaultNudgesService.dismissNudge(NudgeType.AccountSecurity, userId); + } } else { - await this.vaultTimeoutSettingsService.clear(); + const userId = await firstValueFrom(this.accountService.activeAccount$.pipe(getUserId)); + await this.vaultTimeoutSettingsService.clear(userId); } } @@ -501,13 +524,19 @@ export class AccountSecurityComponent implements OnInit, OnDestroy { return; } - await this.keyService.refreshAdditionalKeys(); + try { + const userId = await firstValueFrom(this.accountService.activeAccount$.pipe(getUserId)); + await this.keyService.refreshAdditionalKeys(userId); - const successful = await this.trySetupBiometrics(); - this.form.controls.biometric.setValue(successful); - await this.biometricStateService.setBiometricUnlockEnabled(successful); - if (!successful) { - await this.biometricStateService.setFingerprintValidated(false); + const successful = await this.trySetupBiometrics(); + this.form.controls.biometric.setValue(successful); + await this.biometricStateService.setBiometricUnlockEnabled(successful); + if (!successful) { + await this.biometricStateService.setFingerprintValidated(false); + } + } catch (error) { + this.form.controls.biometric.setValue(false); + this.validationService.showError(error); } } else { await this.biometricStateService.setBiometricUnlockEnabled(false); diff --git a/apps/browser/src/auth/popup/settings/await-desktop-dialog.component.ts b/apps/browser/src/auth/popup/settings/await-desktop-dialog.component.ts index f7c4351dec3..11bb9683bb9 100644 --- a/apps/browser/src/auth/popup/settings/await-desktop-dialog.component.ts +++ b/apps/browser/src/auth/popup/settings/await-desktop-dialog.component.ts @@ -5,7 +5,6 @@ import { ButtonModule, DialogModule, DialogService } from "@bitwarden/components @Component({ templateUrl: "await-desktop-dialog.component.html", - standalone: true, imports: [JslibModule, ButtonModule, DialogModule], }) export class AwaitDesktopDialogComponent { diff --git a/apps/browser/src/auth/popup/settings/vault-timeout-input.component.ts b/apps/browser/src/auth/popup/settings/vault-timeout-input.component.ts index c56e6578a0b..25a4d01333d 100644 --- a/apps/browser/src/auth/popup/settings/vault-timeout-input.component.ts +++ b/apps/browser/src/auth/popup/settings/vault-timeout-input.component.ts @@ -18,5 +18,6 @@ import { VaultTimeoutInputComponent as VaultTimeoutInputComponentBase } from "@b useExisting: VaultTimeoutInputComponent, }, ], + standalone: false, }) export class VaultTimeoutInputComponent extends VaultTimeoutInputComponentBase {} diff --git a/apps/browser/src/auth/popup/update-temp-password.component.ts b/apps/browser/src/auth/popup/update-temp-password.component.ts index 465bc3f7038..e8cf64b7548 100644 --- a/apps/browser/src/auth/popup/update-temp-password.component.ts +++ b/apps/browser/src/auth/popup/update-temp-password.component.ts @@ -8,6 +8,7 @@ import { postLogoutMessageListener$ } from "./utils/post-logout-message-listener @Component({ selector: "app-update-temp-password", templateUrl: "update-temp-password.component.html", + standalone: false, }) export class UpdateTempPasswordComponent extends BaseUpdateTempPasswordComponent { onSuccessfulChangePassword: () => Promise = this.doOnSuccessfulChangePassword.bind(this); 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..9c9c5c0e243 100644 --- a/apps/browser/src/autofill/background/abstractions/notification.background.ts +++ b/apps/browser/src/autofill/background/abstractions/notification.background.ts @@ -1,7 +1,11 @@ import { NeverDomains } from "@bitwarden/common/models/domain/domain-service"; import { ServerConfig } from "@bitwarden/common/platform/abstractions/config/server-config"; +import { UserId } from "@bitwarden/common/types/guid"; +import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view"; import { FolderView } from "@bitwarden/common/vault/models/view/folder.view"; +import { SecurityTask } from "@bitwarden/common/vault/tasks"; +import { CollectionView } from "../../content/components/common-types"; import { NotificationQueueMessageTypes } from "../../enums/notification-queue-message-type.enum"; import AutofillPageDetails from "../../models/autofill-page-details"; @@ -31,10 +35,17 @@ interface AddUnlockVaultQueueMessage extends NotificationQueueMessage { type: "unlock"; } +interface AtRiskPasswordQueueMessage extends NotificationQueueMessage { + type: "at-risk-password"; + organizationName: string; + passwordChangeUri?: string; +} + type NotificationQueueMessageItem = | AddLoginQueueMessage | AddChangePasswordQueueMessage - | AddUnlockVaultQueueMessage; + | AddUnlockVaultQueueMessage + | AtRiskPasswordQueueMessage; type LockedVaultPendingNotificationsData = { commandToRetry: { @@ -49,6 +60,13 @@ type LockedVaultPendingNotificationsData = { target: string; }; +type AtRiskPasswordNotificationsData = { + activeUserId: UserId; + cipher: CipherView; + securityTask: SecurityTask; + uri: string; +}; + type AdjustNotificationBarMessageData = { height: number; }; @@ -75,7 +93,8 @@ type NotificationBackgroundExtensionMessage = { data?: Partial & Partial & Partial & - Partial; + Partial & + Partial; login?: AddLoginMessageData; folder?: string; edit?: boolean; @@ -83,6 +102,7 @@ type NotificationBackgroundExtensionMessage = { tab?: chrome.tabs.Tab; sender?: string; notificationType?: string; + organizationId?: string; fadeOutNotification?: boolean; }; @@ -94,14 +114,35 @@ 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; + bgOpenAtRiskPasswords: ({ message, sender }: BackgroundOnMessageHandlerParams) => Promise; bgAdjustNotificationBar: ({ message, sender }: BackgroundOnMessageHandlerParams) => Promise; - bgAddLogin: ({ message, sender }: BackgroundOnMessageHandlerParams) => Promise; - bgChangedPassword: ({ message, sender }: BackgroundOnMessageHandlerParams) => Promise; + bgTriggerAddLoginNotification: ({ + message, + sender, + }: BackgroundOnMessageHandlerParams) => Promise; + bgTriggerChangedPasswordNotification: ({ + message, + sender, + }: BackgroundOnMessageHandlerParams) => Promise; + bgTriggerAtRiskPasswordNotification: ({ + 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/abstractions/overlay.background.ts b/apps/browser/src/autofill/background/abstractions/overlay.background.ts index 6ad9b8e06fd..5e2b755ad4a 100644 --- a/apps/browser/src/autofill/background/abstractions/overlay.background.ts +++ b/apps/browser/src/autofill/background/abstractions/overlay.background.ts @@ -4,7 +4,7 @@ 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 { InlineMenuFillTypes } from "../../enums/autofill-overlay.enum"; +import { InlineMenuFillType } from "../../enums/autofill-overlay.enum"; import AutofillPageDetails from "../../models/autofill-page-details"; import { PageDetail } from "../../services/abstractions/autofill.service"; @@ -43,7 +43,7 @@ export type UpdateOverlayCiphersParams = { export type FocusedFieldData = { focusedFieldStyles: Partial; focusedFieldRects: Partial; - inlineMenuFillType?: InlineMenuFillTypes; + inlineMenuFillType?: InlineMenuFillType; tabId?: number; frameId?: number; accountCreationFieldType?: string; diff --git a/apps/browser/src/autofill/background/auto-submit-login.background.spec.ts b/apps/browser/src/autofill/background/auto-submit-login.background.spec.ts index 9f197b02193..373354b4c54 100644 --- a/apps/browser/src/autofill/background/auto-submit-login.background.spec.ts +++ b/apps/browser/src/autofill/background/auto-submit-login.background.spec.ts @@ -5,7 +5,6 @@ import { PolicyService } from "@bitwarden/common/admin-console/abstractions/poli import { Policy } from "@bitwarden/common/admin-console/models/domain/policy"; import { AuthService } from "@bitwarden/common/auth/abstractions/auth.service"; import { AuthenticationStatus } from "@bitwarden/common/auth/enums/authentication-status"; -import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.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"; @@ -35,7 +34,6 @@ describe("AutoSubmitLoginBackground", () => { let scriptInjectorService: MockProxy; let authStatus$: BehaviorSubject; let authService: MockProxy; - let configService: MockProxy; let platformUtilsService: MockProxy; let policyDetails: MockProxy; let automaticAppLogInPolicy$: BehaviorSubject; @@ -56,9 +54,6 @@ describe("AutoSubmitLoginBackground", () => { authStatus$ = new BehaviorSubject(AuthenticationStatus.Unlocked); authService = mock(); authService.activeAccountStatus$ = authStatus$; - configService = mock({ - getFeatureFlag: jest.fn().mockResolvedValue(true), - }); platformUtilsService = mock(); policyDetails = mock({ enabled: true, @@ -78,7 +73,6 @@ describe("AutoSubmitLoginBackground", () => { autofillService, scriptInjectorService, authService, - configService, platformUtilsService, policyService, accountService, @@ -89,7 +83,7 @@ describe("AutoSubmitLoginBackground", () => { jest.clearAllMocks(); }); - describe("when the AutoSubmitLoginBackground feature is disabled", () => { + describe("when conditions prevent auto-submit policy activation", () => { it("destroys all event listeners when the AutomaticAppLogIn policy is not enabled", async () => { automaticAppLogInPolicy$.next([mock({ ...policyDetails, enabled: false })]); @@ -115,7 +109,7 @@ describe("AutoSubmitLoginBackground", () => { }); }); - describe("when the AutoSubmitLoginBackground feature is enabled", () => { + describe("when the AutomaticAppLogIn policy is valid and active", () => { let webRequestDetails: chrome.webRequest.WebRequestBodyDetails; describe("starting the auto-submit login workflow", () => { @@ -268,7 +262,6 @@ describe("AutoSubmitLoginBackground", () => { autofillService, scriptInjectorService, authService, - configService, platformUtilsService, policyService, accountService, diff --git a/apps/browser/src/autofill/background/auto-submit-login.background.ts b/apps/browser/src/autofill/background/auto-submit-login.background.ts index bce876e8f82..dcafe21b63c 100644 --- a/apps/browser/src/autofill/background/auto-submit-login.background.ts +++ b/apps/browser/src/autofill/background/auto-submit-login.background.ts @@ -10,8 +10,6 @@ import { AccountService } from "@bitwarden/common/auth/abstractions/account.serv import { AuthService } from "@bitwarden/common/auth/abstractions/auth.service"; import { AuthenticationStatus } from "@bitwarden/common/auth/enums/authentication-status"; 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 { LogService } from "@bitwarden/common/platform/abstractions/log.service"; import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; @@ -42,7 +40,6 @@ export class AutoSubmitLoginBackground implements AutoSubmitLoginBackgroundAbstr private autofillService: AutofillService, private scriptInjectorService: ScriptInjectorService, private authService: AuthService, - private configService: ConfigService, private platformUtilsService: PlatformUtilsService, private policyService: PolicyService, private accountService: AccountService, @@ -51,25 +48,19 @@ export class AutoSubmitLoginBackground implements AutoSubmitLoginBackgroundAbstr } /** - * Initializes the auto-submit login policy. Will return early if - * the feature flag is not set. If the policy is not enabled, it + * Initializes the auto-submit login policy. If the policy is not enabled, it * will trigger a removal of any established listeners. */ async init() { - const featureFlagEnabled = await this.configService.getFeatureFlag( - FeatureFlag.IdpAutoSubmitLogin, - ); - if (featureFlagEnabled) { - this.accountService.activeAccount$ - .pipe( - getUserId, - switchMap((userId) => - this.policyService.policiesByType$(PolicyType.AutomaticAppLogIn, userId), - ), - getFirstPolicy, - ) - .subscribe(this.handleAutoSubmitLoginPolicySubscription.bind(this)); - } + this.accountService.activeAccount$ + .pipe( + getUserId, + switchMap((userId) => + this.policyService.policiesByType$(PolicyType.AutomaticAppLogIn, userId), + ), + getFirstPolicy, + ) + .subscribe(this.handleAutoSubmitLoginPolicySubscription.bind(this)); } /** diff --git a/apps/browser/src/autofill/background/notification.background.spec.ts b/apps/browser/src/autofill/background/notification.background.spec.ts index ffc416ab62a..5e7e3ed30f5 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"; @@ -16,6 +17,7 @@ import { MessagingService } from "@bitwarden/common/platform/abstractions/messag import { SelfHostedEnvironment } from "@bitwarden/common/platform/services/default-environment.service"; import { ThemeStateService } from "@bitwarden/common/platform/theming/theme-state.service"; import { UserId } from "@bitwarden/common/types/guid"; +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 { CipherService } from "@bitwarden/common/vault/services/cipher.service"; @@ -53,6 +55,7 @@ describe("NotificationBackground", () => { let notificationBackground: NotificationBackground; const autofillService = mock(); const cipherService = mock(); + const collectionService = mock(); let activeAccountStatusMock$: BehaviorSubject; let authService: MockProxy; const policyService = mock(); @@ -66,8 +69,9 @@ describe("NotificationBackground", () => { const accountService = mock(); const organizationService = mock(); + const userId = "testId" as UserId; const activeAccountSubject = new BehaviorSubject<{ id: UserId } & AccountInfo>({ - id: "testId" as UserId, + id: userId, email: "test@example.com", emailVerified: true, name: "Test User", @@ -83,6 +87,7 @@ describe("NotificationBackground", () => { authService, autofillService, cipherService, + collectionService, configService, domainSettingsService, environmentService, @@ -270,7 +275,7 @@ describe("NotificationBackground", () => { }); }); - describe("bgAddLogin message handler", () => { + describe("bgTriggerAddLoginNotification message handler", () => { let tab: chrome.tabs.Tab; let sender: chrome.runtime.MessageSender; let getEnableAddedLoginPromptSpy: jest.SpyInstance; @@ -300,7 +305,7 @@ describe("NotificationBackground", () => { it("skips attempting to add the login if the user is logged out", async () => { const message: NotificationBackgroundExtensionMessage = { - command: "bgAddLogin", + command: "bgTriggerAddLoginNotification", login: { username: "test", password: "password", url: "https://example.com" }, }; activeAccountStatusMock$.next(AuthenticationStatus.LoggedOut); @@ -314,7 +319,7 @@ describe("NotificationBackground", () => { it("skips attempting to add the login if the login data does not contain a valid url", async () => { const message: NotificationBackgroundExtensionMessage = { - command: "bgAddLogin", + command: "bgTriggerAddLoginNotification", login: { username: "test", password: "password", url: "" }, }; activeAccountStatusMock$.next(AuthenticationStatus.Locked); @@ -328,7 +333,7 @@ describe("NotificationBackground", () => { it("skips attempting to add the login if the user with a locked vault has disabled the login notification", async () => { const message: NotificationBackgroundExtensionMessage = { - command: "bgAddLogin", + command: "bgTriggerAddLoginNotification", login: { username: "test", password: "password", url: "https://example.com" }, }; activeAccountStatusMock$.next(AuthenticationStatus.Locked); @@ -345,7 +350,7 @@ describe("NotificationBackground", () => { it("skips attempting to add the login if the user with an unlocked vault has disabled the login notification", async () => { const message: NotificationBackgroundExtensionMessage = { - command: "bgAddLogin", + command: "bgTriggerAddLoginNotification", login: { username: "test", password: "password", url: "https://example.com" }, }; activeAccountStatusMock$.next(AuthenticationStatus.Unlocked); @@ -363,7 +368,7 @@ describe("NotificationBackground", () => { it("skips attempting to change the password for an existing login if the user has disabled changing the password notification", async () => { const message: NotificationBackgroundExtensionMessage = { - command: "bgAddLogin", + command: "bgTriggerAddLoginNotification", login: { username: "test", password: "password", url: "https://example.com" }, }; activeAccountStatusMock$.next(AuthenticationStatus.Unlocked); @@ -385,7 +390,7 @@ describe("NotificationBackground", () => { it("skips attempting to change the password for an existing login if the password has not changed", async () => { const message: NotificationBackgroundExtensionMessage = { - command: "bgAddLogin", + command: "bgTriggerAddLoginNotification", login: { username: "test", password: "password", url: "https://example.com" }, }; activeAccountStatusMock$.next(AuthenticationStatus.Unlocked); @@ -405,7 +410,10 @@ describe("NotificationBackground", () => { it("adds the login to the queue if the user has a locked account", async () => { const login = { username: "test", password: "password", url: "https://example.com" }; - const message: NotificationBackgroundExtensionMessage = { command: "bgAddLogin", login }; + const message: NotificationBackgroundExtensionMessage = { + command: "bgTriggerAddLoginNotification", + login, + }; activeAccountStatusMock$.next(AuthenticationStatus.Locked); getEnableAddedLoginPromptSpy.mockReturnValueOnce(true); @@ -421,7 +429,10 @@ describe("NotificationBackground", () => { password: "password", url: "https://example.com", } as any; - const message: NotificationBackgroundExtensionMessage = { command: "bgAddLogin", login }; + const message: NotificationBackgroundExtensionMessage = { + command: "bgTriggerAddLoginNotification", + login, + }; activeAccountStatusMock$.next(AuthenticationStatus.Unlocked); getEnableAddedLoginPromptSpy.mockReturnValueOnce(true); getAllDecryptedForUrlSpy.mockResolvedValueOnce([ @@ -436,7 +447,10 @@ describe("NotificationBackground", () => { it("adds a change password message to the queue if the user has changed an existing cipher's password", async () => { const login = { username: "tEsT", password: "password", url: "https://example.com" }; - const message: NotificationBackgroundExtensionMessage = { command: "bgAddLogin", login }; + const message: NotificationBackgroundExtensionMessage = { + command: "bgTriggerAddLoginNotification", + login, + }; activeAccountStatusMock$.next(AuthenticationStatus.Unlocked); getEnableAddedLoginPromptSpy.mockResolvedValueOnce(true); getEnableChangedPasswordPromptSpy.mockResolvedValueOnce(true); @@ -459,7 +473,7 @@ describe("NotificationBackground", () => { }); }); - describe("bgChangedPassword message handler", () => { + describe("bgTriggerChangedPasswordNotification message handler", () => { let tab: chrome.tabs.Tab; let sender: chrome.runtime.MessageSender; let pushChangePasswordToQueueSpy: jest.SpyInstance; @@ -477,7 +491,7 @@ describe("NotificationBackground", () => { it("skips attempting to add the change password message to the queue if the passed url is not valid", async () => { const message: NotificationBackgroundExtensionMessage = { - command: "bgChangedPassword", + command: "bgTriggerChangedPasswordNotification", data: { newPassword: "newPassword", currentPassword: "currentPassword", url: "" }, }; @@ -489,7 +503,7 @@ describe("NotificationBackground", () => { it("adds a change password message to the queue if the user does not have an unlocked account", async () => { const message: NotificationBackgroundExtensionMessage = { - command: "bgChangedPassword", + command: "bgTriggerChangedPasswordNotification", data: { newPassword: "newPassword", currentPassword: "currentPassword", @@ -512,7 +526,7 @@ describe("NotificationBackground", () => { it("skips adding a change password message to the queue if the multiple ciphers exist for the passed URL and the current password is not found within the list of ciphers", async () => { const message: NotificationBackgroundExtensionMessage = { - command: "bgChangedPassword", + command: "bgTriggerChangedPasswordNotification", data: { newPassword: "newPassword", currentPassword: "currentPassword", @@ -533,7 +547,7 @@ describe("NotificationBackground", () => { it("skips adding a change password message if more than one existing cipher is found with a matching password ", async () => { const message: NotificationBackgroundExtensionMessage = { - command: "bgChangedPassword", + command: "bgTriggerChangedPasswordNotification", data: { newPassword: "newPassword", currentPassword: "currentPassword", @@ -555,7 +569,7 @@ describe("NotificationBackground", () => { it("adds a change password message to the queue if a single cipher matches the passed current password", async () => { const message: NotificationBackgroundExtensionMessage = { - command: "bgChangedPassword", + command: "bgTriggerChangedPasswordNotification", data: { newPassword: "newPassword", currentPassword: "currentPassword", @@ -583,7 +597,7 @@ describe("NotificationBackground", () => { it("skips adding a change password message if no current password is passed in the message and more than one cipher is found for a url", async () => { const message: NotificationBackgroundExtensionMessage = { - command: "bgChangedPassword", + command: "bgTriggerChangedPasswordNotification", data: { newPassword: "newPassword", url: "https://example.com", @@ -604,7 +618,7 @@ describe("NotificationBackground", () => { it("adds a change password message to the queue if no current password is passed with the message, but a single cipher is matched for the uri", async () => { const message: NotificationBackgroundExtensionMessage = { - command: "bgChangedPassword", + command: "bgTriggerChangedPasswordNotification", data: { newPassword: "newPassword", url: "https://example.com", @@ -823,7 +837,9 @@ describe("NotificationBackground", () => { notificationBackground["notificationQueue"] = [queueMessage]; const cipherView = mock({ id: "testId", + name: "testItemName", login: { username: "testUser" }, + reprompt: CipherRepromptType.None, }); getDecryptedCipherByIdSpy.mockResolvedValueOnce(cipherView); @@ -838,14 +854,65 @@ describe("NotificationBackground", () => { message.edit, sender.tab, "testId", + false, ); expect(updateWithServerSpy).toHaveBeenCalled(); expect(tabSendMessageDataSpy).toHaveBeenCalledWith( sender.tab, "saveCipherAttemptCompleted", { - username: cipherView.login.username, + itemName: "testItemName", cipherId: cipherView.id, + task: undefined, + }, + ); + }); + + it("prompts the user for master password entry if the notification message type is for ChangePassword and the cipher reprompt is enabled", async () => { + const tab = createChromeTabMock({ id: 1, url: "https://example.com" }); + const sender = mock({ tab }); + const message: NotificationBackgroundExtensionMessage = { + command: "bgSaveCipher", + edit: false, + folder: "folder-id", + }; + const queueMessage = mock({ + type: NotificationQueueMessageType.ChangePassword, + tab, + domain: "example.com", + newPassword: "newPassword", + }); + notificationBackground["notificationQueue"] = [queueMessage]; + const cipherView = mock({ + id: "testId", + name: "testItemName", + login: { username: "testUser" }, + reprompt: CipherRepromptType.Password, + }); + getDecryptedCipherByIdSpy.mockResolvedValueOnce(cipherView); + + sendMockExtensionMessage(message, sender); + await flushPromises(); + + expect(editItemSpy).not.toHaveBeenCalled(); + expect(autofillService.isPasswordRepromptRequired).toHaveBeenCalled(); + expect(createWithServerSpy).not.toHaveBeenCalled(); + expect(updatePasswordSpy).toHaveBeenCalledWith( + cipherView, + queueMessage.newPassword, + message.edit, + sender.tab, + "testId", + false, + ); + expect(updateWithServerSpy).not.toHaveBeenCalled(); + expect(tabSendMessageDataSpy).not.toHaveBeenCalledWith( + sender.tab, + "saveCipherAttemptCompleted", + { + itemName: "testItemName", + cipherId: cipherView.id, + task: undefined, }, ); }); @@ -899,7 +966,8 @@ describe("NotificationBackground", () => { const cipherView = mock({ id: mockCipherId, organizationId: mockOrgId, - login: { username: "testUser" }, + name: "Test Item", + reprompt: CipherRepromptType.None, }); getDecryptedCipherByIdSpy.mockResolvedValueOnce(cipherView); @@ -914,6 +982,7 @@ describe("NotificationBackground", () => { message.edit, sender.tab, mockCipherId, + false, ); expect(updateWithServerSpy).toHaveBeenCalled(); expect(tabSendMessageDataSpy).toHaveBeenCalledWith( @@ -921,11 +990,11 @@ describe("NotificationBackground", () => { "saveCipherAttemptCompleted", { cipherId: "testId", + itemName: "Test Item", task: { orgName: "Org Name, LLC", remainingTasksCount: 1, }, - username: "testUser", }, ); }); @@ -995,6 +1064,7 @@ describe("NotificationBackground", () => { message.edit, sender.tab, "testId", + false, ); expect(editItemSpy).toHaveBeenCalled(); expect(updateWithServerSpy).not.toHaveBeenCalled(); @@ -1074,14 +1144,18 @@ describe("NotificationBackground", () => { notificationBackground["notificationQueue"] = [queueMessage]; const cipherView = mock({ id: "testId", + name: "testName", login: { username: "test", password: "password" }, }); folderExistsSpy.mockResolvedValueOnce(false); convertAddLoginQueueMessageToCipherViewSpy.mockReturnValueOnce(cipherView); editItemSpy.mockResolvedValueOnce(undefined); cipherEncryptSpy.mockResolvedValueOnce({ - ...cipherView, - id: "testId", + cipher: { + ...cipherView, + id: "testId", + }, + encryptedFor: userId, }); sendMockExtensionMessage(message, sender); @@ -1097,8 +1171,8 @@ describe("NotificationBackground", () => { sender.tab, "saveCipherAttemptCompleted", { - username: cipherView.login.username, cipherId: cipherView.id, + itemName: cipherView.name, }, ); expect(tabSendMessageSpy).toHaveBeenCalledWith(sender.tab, { command: "addedCipher" }); @@ -1127,6 +1201,13 @@ describe("NotificationBackground", () => { folderExistsSpy.mockResolvedValueOnce(true); convertAddLoginQueueMessageToCipherViewSpy.mockReturnValueOnce(cipherView); editItemSpy.mockResolvedValueOnce(undefined); + cipherEncryptSpy.mockResolvedValueOnce({ + cipher: { + ...cipherView, + id: "testId", + }, + encryptedFor: userId, + }); const errorMessage = "fetch error"; createWithServerSpy.mockImplementation(() => { throw new Error(errorMessage); @@ -1164,7 +1245,7 @@ describe("NotificationBackground", () => { newPassword: "newPassword", }); notificationBackground["notificationQueue"] = [queueMessage]; - const cipherView = mock(); + const cipherView = mock({ reprompt: CipherRepromptType.None }); getDecryptedCipherByIdSpy.mockResolvedValueOnce(cipherView); const errorMessage = "fetch error"; updateWithServerSpy.mockImplementation(() => { diff --git a/apps/browser/src/autofill/background/notification.background.ts b/apps/browser/src/autofill/background/notification.background.ts index 6589252d94b..3c63d423aaa 100644 --- a/apps/browser/src/autofill/background/notification.background.ts +++ b/apps/browser/src/autofill/background/notification.background.ts @@ -1,8 +1,12 @@ // 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 { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction"; +import { CollectionService } from "@bitwarden/admin-console/common"; +import { + getOrganizationById, + 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"; import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; @@ -13,6 +17,7 @@ import { ExtensionCommand, ExtensionCommandType, NOTIFICATION_BAR_LIFESPAN_MS, + UPDATE_PASSWORD, } from "@bitwarden/common/autofill/constants"; import { DomainSettingsService } from "@bitwarden/common/autofill/services/domain-settings.service"; import { UserNotificationSettingsServiceAbstraction } from "@bitwarden/common/autofill/services/user-notification-settings.service"; @@ -41,14 +46,19 @@ 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"; +import { TemporaryNotificationChangeLoginService } from "../services/notification-change-login-password.service"; import { AddChangePasswordQueueMessage, @@ -67,6 +77,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, @@ -74,27 +85,36 @@ export default class NotificationBackground { ExtensionCommand.AutofillIdentity, ]); private readonly extensionMessageHandlers: NotificationBackgroundExtensionMessageHandlers = { - bgAddLogin: ({ message, sender }) => this.addLogin(message, sender), bgAdjustNotificationBar: ({ message, sender }) => this.handleAdjustNotificationBarMessage(message, sender), - bgChangedPassword: ({ message, sender }) => this.changedPassword(message, sender), + bgTriggerAddLoginNotification: ({ message, sender }) => + this.triggerAddLoginNotification(message, sender), + bgTriggerChangedPasswordNotification: ({ message, sender }) => + this.triggerChangedPasswordNotification(message, sender), + bgTriggerAtRiskPasswordNotification: ({ message, sender }) => + this.triggerAtRiskPasswordNotification(message, sender), bgCloseNotificationBar: ({ message, sender }) => this.handleCloseNotificationBarMessage(message, sender), - bgOpenAtRisksPasswords: ({ message, sender }) => - this.handleOpenAtRisksPasswordsMessage(message, sender), + bgOpenAtRiskPasswords: ({ message, sender }) => + this.handleOpenAtRiskPasswordsMessage(message, sender), bgGetActiveUserServerConfig: () => this.getActiveUserServerConfig(), bgGetDecryptedCiphers: () => this.getNotificationCipherData(), bgGetEnableChangedPasswordPrompt: () => this.getEnableChangedPasswordPrompt(), 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), bgSaveCipher: ({ message, sender }) => this.handleSaveCipherMessage(message, sender), + bgHandleReprompt: ({ message, sender }: any) => + this.handleCipherUpdateRepromptResponse(message), bgUnlockPopoutOpened: ({ message, sender }) => this.unlockVault(message, sender.tab), checkNotificationQueue: ({ sender }) => this.checkNotificationQueue(sender.tab), collectPageDetailsResponse: ({ message }) => @@ -109,6 +129,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 +176,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 +264,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 }, + }; } /** @@ -285,12 +349,17 @@ export default class NotificationBackground { tab: chrome.tabs.Tab, notificationQueueMessage: NotificationQueueMessageItem, ) { - const notificationType = notificationQueueMessage.type; + const { + type: notificationType, + wasVaultLocked: isVaultLocked, + launchTimestamp, + ...params + } = notificationQueueMessage; const typeData: NotificationTypeData = { - isVaultLocked: notificationQueueMessage.wasVaultLocked, + isVaultLocked, theme: await firstValueFrom(this.themeStateService.selectedTheme$), - launchTimestamp: notificationQueueMessage.launchTimestamp, + launchTimestamp, }; switch (notificationType) { @@ -302,6 +371,7 @@ export default class NotificationBackground { await BrowserApi.tabSendMessageData(tab, "openNotificationBar", { type: notificationType, typeData, + params, }); } @@ -319,6 +389,48 @@ export default class NotificationBackground { } } + /** + * Sends a message to trigger the at risk password notification + * + * @param message - The extension message + * @param sender - The contextual sender of the message + */ + async triggerAtRiskPasswordNotification( + message: NotificationBackgroundExtensionMessage, + sender: chrome.runtime.MessageSender, + ): Promise { + const { activeUserId, securityTask, cipher } = message.data; + const domain = Utils.getDomain(sender.tab.url); + const passwordChangeUri = + await new TemporaryNotificationChangeLoginService().getChangePasswordUrl(cipher); + + const authStatus = await this.getAuthStatus(); + + const wasVaultLocked = authStatus === AuthenticationStatus.Locked; + + const organization = await firstValueFrom( + this.organizationService + .organizations$(activeUserId) + .pipe(getOrganizationById(securityTask.organizationId)), + ); + + this.removeTabFromNotificationQueue(sender.tab); + const launchTimestamp = new Date().getTime(); + const queueMessage: NotificationQueueMessageItem = { + domain, + wasVaultLocked, + type: NotificationQueueMessageType.AtRiskPassword, + passwordChangeUri, + organizationName: organization.name, + tab: sender.tab, + launchTimestamp, + expires: new Date(launchTimestamp + NOTIFICATION_BAR_LIFESPAN_MS), + }; + this.notificationQueue.push(queueMessage); + await this.checkNotificationQueue(sender.tab); + return true; + } + /** * Adds a login message to the notification queue, prompting the user to save * the login if it does not already exist in the vault. If the cipher exists @@ -327,20 +439,20 @@ export default class NotificationBackground { * @param message - The message to add to the queue * @param sender - The contextual sender of the message */ - async addLogin( + async triggerAddLoginNotification( message: NotificationBackgroundExtensionMessage, sender: chrome.runtime.MessageSender, - ) { + ): Promise { const authStatus = await this.getAuthStatus(); if (authStatus === AuthenticationStatus.LoggedOut) { - return; + return false; } const loginInfo = message.login; const normalizedUsername = loginInfo.username ? loginInfo.username.toLowerCase() : ""; const loginDomain = Utils.getDomain(loginInfo.url); if (loginDomain == null) { - return; + return false; } const addLoginIsEnabled = await this.getEnableAddedLoginPrompt(); @@ -350,14 +462,14 @@ export default class NotificationBackground { await this.pushAddLoginToQueue(loginDomain, loginInfo, sender.tab, true); } - return; + return false; } const activeUserId = await firstValueFrom( this.accountService.activeAccount$.pipe(getOptionalUserId), ); if (activeUserId == null) { - return; + return false; } const ciphers = await this.cipherService.getAllDecryptedForUrl(loginInfo.url, activeUserId); @@ -366,7 +478,7 @@ export default class NotificationBackground { ); if (addLoginIsEnabled && usernameMatches.length === 0) { await this.pushAddLoginToQueue(loginDomain, loginInfo, sender.tab); - return; + return true; } const changePasswordIsEnabled = await this.getEnableChangedPasswordPrompt(); @@ -382,7 +494,9 @@ export default class NotificationBackground { loginInfo.password, sender.tab, ); + return true; } + return false; } private async pushAddLoginToQueue( @@ -416,14 +530,14 @@ export default class NotificationBackground { * @param message - The message to add to the queue * @param sender - The contextual sender of the message */ - async changedPassword( + async triggerChangedPasswordNotification( message: NotificationBackgroundExtensionMessage, sender: chrome.runtime.MessageSender, ) { const changeData = message.data as ChangePasswordMessageData; const loginDomain = Utils.getDomain(changeData.url); if (loginDomain == null) { - return; + return false; } if ((await this.getAuthStatus()) < AuthenticationStatus.Unlocked) { @@ -434,7 +548,7 @@ export default class NotificationBackground { sender.tab, true, ); - return; + return true; } let id: string = null; @@ -442,7 +556,7 @@ export default class NotificationBackground { this.accountService.activeAccount$.pipe(getOptionalUserId), ); if (activeUserId == null) { - return; + return false; } const ciphers = await this.cipherService.getAllDecryptedForUrl(changeData.url, activeUserId); @@ -458,7 +572,9 @@ export default class NotificationBackground { } if (id != null) { await this.pushChangePasswordToQueue(id, loginDomain, changeData.newPassword, sender.tab); + return true; } + return false; } /** @@ -489,10 +605,15 @@ export default class NotificationBackground { * @param tab - The tab that the message was sent from */ private async unlockVault(message: NotificationBackgroundExtensionMessage, tab: chrome.tabs.Tab) { + const notificationRefreshFlagEnabled = await this.getNotificationFlag(); if (message.data?.skipNotification) { return; } + if (notificationRefreshFlagEnabled) { + return; + } + const currentAuthStatus = await this.getAuthStatus(); if (currentAuthStatus !== AuthenticationStatus.Locked || this.notificationQueue.length) { return; @@ -573,6 +694,17 @@ export default class NotificationBackground { await this.saveOrUpdateCredentials(sender.tab, message.edit, message.folder); } + async handleCipherUpdateRepromptResponse(message: NotificationBackgroundExtensionMessage) { + if (message.success) { + await this.saveOrUpdateCredentials(message.tab, false, undefined, true); + } else { + await BrowserApi.tabSendMessageData(message.tab, "saveCipherAttemptCompleted", { + error: "Password reprompt failed", + }); + return; + } + } + /** * Saves or updates credentials based on the message within the * notification queue that is associated with the specified tab. @@ -581,7 +713,12 @@ export default class NotificationBackground { * @param edit - Identifies if the credentials should be edited or simply added * @param folderId - The folder to add the cipher to */ - private async saveOrUpdateCredentials(tab: chrome.tabs.Tab, edit: boolean, folderId?: string) { + private async saveOrUpdateCredentials( + tab: chrome.tabs.Tab, + edit: boolean, + folderId?: string, + skipReprompt: boolean = false, + ) { for (let i = this.notificationQueue.length - 1; i >= 0; i--) { const queueMessage = this.notificationQueue[i]; if ( @@ -596,18 +733,26 @@ export default class NotificationBackground { continue; } - this.notificationQueue.splice(i, 1); - const activeUserId = await firstValueFrom( this.accountService.activeAccount$.pipe(getOptionalUserId), ); if (queueMessage.type === NotificationQueueMessageType.ChangePassword) { const cipherView = await this.getDecryptedCipherById(queueMessage.cipherId, activeUserId); - await this.updatePassword(cipherView, queueMessage.newPassword, edit, tab, activeUserId); + + await this.updatePassword( + cipherView, + queueMessage.newPassword, + edit, + tab, + activeUserId, + skipReprompt, + ); return; } + this.notificationQueue.splice(i, 1); + // If the vault was locked, check if a cipher needs updating instead of creating a new one if (queueMessage.wasVaultLocked) { const allCiphers = await this.cipherService.getAllDecryptedForUrl( @@ -634,12 +779,13 @@ export default class NotificationBackground { return; } - const cipher = await this.cipherService.encrypt(newCipher, activeUserId); + const encrypted = await this.cipherService.encrypt(newCipher, activeUserId); + const { cipher } = encrypted; try { - await this.cipherService.createWithServer(cipher); + await this.cipherService.createWithServer(encrypted); 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) { @@ -667,6 +813,7 @@ export default class NotificationBackground { edit: boolean, tab: chrome.tabs.Tab, userId: UserId, + skipReprompt: boolean = false, ) { cipherView.login.password = newPassword; @@ -679,8 +826,10 @@ export default class NotificationBackground { const cipher = await this.cipherService.encrypt(cipherView, userId); const shouldGetTasks = await this.getNotificationFlag(); - try { + if (!cipherView.edit) { + throw new Error("You do not have permission to edit this cipher."); + } const tasks = shouldGetTasks ? await this.getSecurityTasks(userId) : []; const updatedCipherTask = tasks.find((task) => task.cipherId === cipherView?.id); const cipherHasTask = !!updatedCipherTask?.id; @@ -698,10 +847,16 @@ export default class NotificationBackground { } : undefined; + if (cipherView.reprompt && !skipReprompt) { + await this.autofillService.isPasswordRepromptRequired(cipherView, tab, UPDATE_PASSWORD); + + return; + } + 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 +896,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) { @@ -765,14 +955,12 @@ export default class NotificationBackground { private async getDecryptedCipherById(cipherId: string, userId: UserId) { const cipher = await this.cipherService.get(cipherId, userId); if (cipher != null && cipher.type === CipherType.Login) { - return await cipher.decrypt( - await this.cipherService.getKeyForCipherKeyDecryption(cipher, userId), - ); + return await this.cipherService.decrypt(cipher, userId); } return null; } - private async getSecurityTasks(userId: UserId) { + async getSecurityTasks(userId: UserId) { let tasks: SecurityTask[] = []; if (userId) { @@ -780,7 +968,7 @@ export default class NotificationBackground { this.taskService.tasksEnabled$(userId).pipe( switchMap((tasksEnabled) => { if (!tasksEnabled) { - return []; + return of([]); } return this.taskService @@ -835,6 +1023,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 +1068,7 @@ export default class NotificationBackground { const organizations = await firstValueFrom( this.organizationService.organizations$(activeUserId), ); + return organizations.map((org) => { const { id, name, productTierType } = org; return { @@ -926,7 +1134,7 @@ export default class NotificationBackground { * @param message - The extension message * @param sender - The contextual sender of the message */ - private async handleOpenAtRisksPasswordsMessage( + private async handleOpenAtRiskPasswordsMessage( message: NotificationBackgroundExtensionMessage, sender: chrome.runtime.MessageSender, ) { @@ -991,6 +1199,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-notifications.background.spec.ts b/apps/browser/src/autofill/background/overlay-notifications.background.spec.ts index a51757dabea..00114330bc4 100644 --- a/apps/browser/src/autofill/background/overlay-notifications.background.spec.ts +++ b/apps/browser/src/autofill/background/overlay-notifications.background.spec.ts @@ -1,9 +1,12 @@ import { mock, MockProxy } from "jest-mock-extended"; +import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; import { CLEAR_NOTIFICATION_LOGIN_DATA_DURATION } from "@bitwarden/common/autofill/constants"; import { ServerConfig } from "@bitwarden/common/platform/abstractions/config/server-config"; import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; import { EnvironmentServerConfigData } from "@bitwarden/common/platform/models/data/server-config.data"; +import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service"; +import { TaskService } from "@bitwarden/common/vault/tasks"; import { BrowserApi } from "../../platform/browser/browser-api"; import AutofillField from "../models/autofill-field"; @@ -24,6 +27,9 @@ import { OverlayNotificationsBackground } from "./overlay-notifications.backgrou describe("OverlayNotificationsBackground", () => { let logService: MockProxy; let notificationBackground: NotificationBackground; + let taskService: TaskService; + let accountService: AccountService; + let cipherService: CipherService; let getEnableChangedPasswordPromptSpy: jest.SpyInstance; let getEnableAddedLoginPromptSpy: jest.SpyInstance; let overlayNotificationsBackground: OverlayNotificationsBackground; @@ -32,6 +38,9 @@ describe("OverlayNotificationsBackground", () => { jest.useFakeTimers(); logService = mock(); notificationBackground = mock(); + taskService = mock(); + accountService = mock(); + cipherService = mock(); getEnableChangedPasswordPromptSpy = jest .spyOn(notificationBackground, "getEnableChangedPasswordPrompt") .mockResolvedValue(true); @@ -41,6 +50,9 @@ describe("OverlayNotificationsBackground", () => { overlayNotificationsBackground = new OverlayNotificationsBackground( logService, notificationBackground, + taskService, + accountService, + cipherService, ); await overlayNotificationsBackground.init(); }); @@ -329,8 +341,11 @@ describe("OverlayNotificationsBackground", () => { tab: { id: 1 }, url: "https://example.com", }); - notificationChangedPasswordSpy = jest.spyOn(notificationBackground, "changedPassword"); - notificationAddLoginSpy = jest.spyOn(notificationBackground, "addLogin"); + notificationChangedPasswordSpy = jest.spyOn( + notificationBackground, + "triggerChangedPasswordNotification", + ); + notificationAddLoginSpy = jest.spyOn(notificationBackground, "triggerAddLoginNotification"); sendMockExtensionMessage( { command: "collectPageDetailsResponse", details: pageDetails }, diff --git a/apps/browser/src/autofill/background/overlay-notifications.background.ts b/apps/browser/src/autofill/background/overlay-notifications.background.ts index 5c85ce132d7..93357113fc4 100644 --- a/apps/browser/src/autofill/background/overlay-notifications.background.ts +++ b/apps/browser/src/autofill/background/overlay-notifications.background.ts @@ -1,9 +1,15 @@ // FIXME: Update this file to be type safe and remove this and next line // @ts-strict-ignore -import { Subject, switchMap, timer } from "rxjs"; +import { firstValueFrom, Subject, switchMap, timer } from "rxjs"; +import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; +import { getOptionalUserId } from "@bitwarden/common/auth/services/account.service"; import { CLEAR_NOTIFICATION_LOGIN_DATA_DURATION } from "@bitwarden/common/autofill/constants"; import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; +import { UserId } from "@bitwarden/common/types/guid"; +import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service"; +import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view"; +import { SecurityTask, SecurityTaskStatus, TaskService } from "@bitwarden/common/vault/tasks"; import { BrowserApi } from "../../platform/browser/browser-api"; import { generateDomainMatchPatterns, isInvalidResponseStatusCode } from "../utils"; @@ -19,6 +25,12 @@ import { } from "./abstractions/overlay-notifications.background"; import NotificationBackground from "./notification.background"; +type LoginSecurityTaskInfo = { + securityTask: SecurityTask; + cipher: CipherView; + uri: ModifyLoginCipherFormData["uri"]; +}; + export class OverlayNotificationsBackground implements OverlayNotificationsBackgroundInterface { private websiteOriginsWithFields: WebsiteOriginsWithFields = new Map(); private activeFormSubmissionRequests: ActiveFormSubmissionRequests = new Set(); @@ -35,6 +47,9 @@ export class OverlayNotificationsBackground implements OverlayNotificationsBackg constructor( private logService: LogService, private notificationBackground: NotificationBackground, + private taskService: TaskService, + private accountService: AccountService, + private cipherService: CipherService, ) {} /** @@ -259,8 +274,8 @@ export class OverlayNotificationsBackground implements OverlayNotificationsBackg const modifyLoginData = this.modifyLoginCipherFormData.get(tabId); return ( !modifyLoginData || - !this.shouldTriggerAddLoginNotification(modifyLoginData) || - !this.shouldTriggerChangePasswordNotification(modifyLoginData) + !this.shouldAttemptAddLoginNotification(modifyLoginData) || + !this.shouldAttemptChangedPasswordNotification(modifyLoginData) ); }; @@ -404,10 +419,11 @@ export class OverlayNotificationsBackground implements OverlayNotificationsBackg modifyLoginData: ModifyLoginCipherFormData, tab: chrome.tabs.Tab, ) => { - if (this.shouldTriggerChangePasswordNotification(modifyLoginData)) { + let result: string; + if (this.shouldAttemptChangedPasswordNotification(modifyLoginData)) { // These notifications are temporarily setup as "messages" to the notification background. // This will be structured differently in a future refactor. - await this.notificationBackground.changedPassword( + const success = await this.notificationBackground.triggerChangedPasswordNotification( { command: "bgChangedPassword", data: { @@ -418,14 +434,15 @@ export class OverlayNotificationsBackground implements OverlayNotificationsBackg }, { tab }, ); - this.clearCompletedWebRequest(requestId, tab); - return; + if (!success) { + result = "Unqualified changedPassword notification attempt."; + } } - if (this.shouldTriggerAddLoginNotification(modifyLoginData)) { - await this.notificationBackground.addLogin( + if (this.shouldAttemptAddLoginNotification(modifyLoginData)) { + const success = await this.notificationBackground.triggerAddLoginNotification( { - command: "bgAddLogin", + command: "bgTriggerAddLoginNotification", login: { url: modifyLoginData.uri, username: modifyLoginData.username, @@ -434,8 +451,44 @@ export class OverlayNotificationsBackground implements OverlayNotificationsBackg }, { tab }, ); - this.clearCompletedWebRequest(requestId, tab); + if (!success) { + result = "Unqualified addLogin notification attempt."; + } } + + const shouldGetTasks = + (await this.notificationBackground.getNotificationFlag()) && !modifyLoginData.newPassword; + + if (shouldGetTasks) { + const activeUserId = await firstValueFrom( + this.accountService.activeAccount$.pipe(getOptionalUserId), + ); + + if (activeUserId) { + const loginSecurityTaskInfo = await this.getSecurityTaskAndCipherForLoginData( + modifyLoginData, + activeUserId, + ); + + if (loginSecurityTaskInfo) { + await this.notificationBackground.triggerAtRiskPasswordNotification( + { + command: "bgTriggerAtRiskPasswordNotification", + data: { + activeUserId, + cipher: loginSecurityTaskInfo.cipher, + securityTask: loginSecurityTaskInfo.securityTask, + }, + }, + { tab }, + ); + } else { + result = "Unqualified atRiskPassword notification attempt."; + } + } + } + this.clearCompletedWebRequest(requestId, tab); + return result; }; /** @@ -443,7 +496,7 @@ export class OverlayNotificationsBackground implements OverlayNotificationsBackg * * @param modifyLoginData - The modified login form data */ - private shouldTriggerChangePasswordNotification = ( + private shouldAttemptChangedPasswordNotification = ( modifyLoginData: ModifyLoginCipherFormData, ) => { return modifyLoginData?.newPassword && !modifyLoginData.username; @@ -454,10 +507,66 @@ export class OverlayNotificationsBackground implements OverlayNotificationsBackg * * @param modifyLoginData - The modified login form data */ - private shouldTriggerAddLoginNotification = (modifyLoginData: ModifyLoginCipherFormData) => { + private shouldAttemptAddLoginNotification = (modifyLoginData: ModifyLoginCipherFormData) => { return modifyLoginData?.username && (modifyLoginData.password || modifyLoginData.newPassword); }; + /** + * If there is a security task for this cipher at login, return the task, cipher view, and uri. + * + * @param modifyLoginData - The modified login form data + * @param activeUserId - The currently logged in user ID + */ + private async getSecurityTaskAndCipherForLoginData( + modifyLoginData: ModifyLoginCipherFormData, + activeUserId: UserId, + ): Promise { + const tasks: SecurityTask[] = await this.notificationBackground.getSecurityTasks(activeUserId); + if (!tasks?.length) { + return null; + } + + const urlCiphers: CipherView[] = await this.cipherService.getAllDecryptedForUrl( + modifyLoginData.uri, + activeUserId, + ); + if (!urlCiphers?.length) { + return null; + } + + const securityTaskForLogin = urlCiphers.reduce( + (taskInfo: LoginSecurityTaskInfo | null, cipher: CipherView) => { + if ( + // exit early if info was found already + taskInfo || + // exit early if the cipher was deleted + cipher.deletedDate || + // exit early if the entered login info doesn't match an existing cipher + modifyLoginData.username !== cipher.login.username || + modifyLoginData.password !== cipher.login.password + ) { + return taskInfo; + } + + // Find the first security task for the cipherId belonging to the entered login + const cipherSecurityTask = tasks.find( + ({ cipherId, status }) => + cipher.id === cipherId && // match security task cipher id to url cipher id + status === SecurityTaskStatus.Pending, // security task has not been completed + ); + + if (cipherSecurityTask) { + return { securityTask: cipherSecurityTask, cipher, uri: modifyLoginData.uri }; + } + + return taskInfo; + }, + null, + ); + + return securityTaskForLogin; + } + /** * Clears the completed web request and removes the modified login form data for the tab. * diff --git a/apps/browser/src/autofill/background/overlay.background.spec.ts b/apps/browser/src/autofill/background/overlay.background.spec.ts index 0fe4a459048..92b2135c973 100644 --- a/apps/browser/src/autofill/background/overlay.background.spec.ts +++ b/apps/browser/src/autofill/background/overlay.background.spec.ts @@ -45,7 +45,7 @@ import { AutofillOverlayElement, AutofillOverlayPort, InlineMenuAccountCreationFieldType, - InlineMenuFillType, + InlineMenuFillTypes, MAX_SUB_FRAME_DEPTH, RedirectFocusDirection, } from "../enums/autofill-overlay.enum"; @@ -1025,7 +1025,7 @@ describe("OverlayBackground", () => { overlayBackground["focusedFieldData"] = createFocusedFieldDataMock({ tabId: tab.id, accountCreationFieldType: "text", - inlineMenuFillType: InlineMenuFillType.AccountCreationUsername, + inlineMenuFillType: InlineMenuFillTypes.AccountCreationUsername, }); cipherService.getAllDecryptedForUrl.mockResolvedValue([loginCipher1, identityCipher]); cipherService.sortCiphersByLastUsedThenName.mockReturnValue(-1); @@ -1383,7 +1383,7 @@ describe("OverlayBackground", () => { { command: "updateFocusedFieldData", focusedFieldData: createFocusedFieldDataMock({ - inlineMenuFillType: InlineMenuFillType.CurrentPasswordUpdate, + inlineMenuFillType: InlineMenuFillTypes.CurrentPasswordUpdate, }), }, mock({ tab }), @@ -2045,7 +2045,7 @@ describe("OverlayBackground", () => { }); it("displays the password generator when the focused field is for password generation", async () => { - focusedFieldData.inlineMenuFillType = InlineMenuFillType.PasswordGeneration; + focusedFieldData.inlineMenuFillType = InlineMenuFillTypes.PasswordGeneration; sendMockExtensionMessage({ command: "updateFocusedFieldData", focusedFieldData }, sender); await flushPromises(); @@ -2103,7 +2103,7 @@ describe("OverlayBackground", () => { }); it("shows the save login menu when the focused field type is for password generation and the field is filled", async () => { - focusedFieldData.inlineMenuFillType = InlineMenuFillType.PasswordGeneration; + focusedFieldData.inlineMenuFillType = InlineMenuFillTypes.PasswordGeneration; sendMockExtensionMessage( { command: "updateFocusedFieldData", focusedFieldData, focusedFieldHasValue: true }, @@ -3409,7 +3409,7 @@ describe("OverlayBackground", () => { { command: "updateFocusedFieldData", focusedFieldData: createFocusedFieldDataMock({ - inlineMenuFillType: InlineMenuFillType.CurrentPasswordUpdate, + inlineMenuFillType: InlineMenuFillTypes.CurrentPasswordUpdate, }), }, sender, @@ -3607,7 +3607,7 @@ describe("OverlayBackground", () => { describe("fillGeneratedPassword", () => { const focusedFieldData = createFocusedFieldDataMock({ - inlineMenuFillType: InlineMenuFillType.PasswordGeneration, + inlineMenuFillType: InlineMenuFillTypes.PasswordGeneration, }); beforeEach(() => { diff --git a/apps/browser/src/autofill/background/overlay.background.ts b/apps/browser/src/autofill/background/overlay.background.ts index 4e2e773a0c7..2ff08328e3d 100644 --- a/apps/browser/src/autofill/background/overlay.background.ts +++ b/apps/browser/src/autofill/background/overlay.background.ts @@ -459,7 +459,7 @@ export class OverlayBackground implements OverlayBackgroundInterface { const cipherView = cipherViews[cipherIndex]; if ( !this.cardAndIdentityCiphers.has(cipherView) && - [CipherType.Card, CipherType.Identity].includes(cipherView.type) + ([CipherType.Card, CipherType.Identity] as CipherType[]).includes(cipherView.type) ) { this.cardAndIdentityCiphers.add(cipherView); } @@ -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 ); } @@ -794,7 +797,7 @@ export class OverlayBackground implements OverlayBackgroundInterface { * @param focusedFieldData - Optional focused field data to validate against */ private focusedFieldMatchesFillType( - fillType: InlineMenuFillTypes, + fillType: InlineMenuFillType, focusedFieldData?: FocusedFieldData, ) { const focusedFieldFillType = focusedFieldData @@ -803,7 +806,7 @@ export class OverlayBackground implements OverlayBackgroundInterface { // When updating the current password for a field, it should fill with a login cipher if ( - focusedFieldFillType === InlineMenuFillType.CurrentPasswordUpdate && + focusedFieldFillType === InlineMenuFillTypes.CurrentPasswordUpdate && fillType === CipherType.Login ) { return true; @@ -816,7 +819,7 @@ export class OverlayBackground implements OverlayBackgroundInterface { * Identifies whether the inline menu is being shown on an account creation field. */ private shouldShowInlineMenuAccountCreation(): boolean { - if (this.focusedFieldMatchesFillType(InlineMenuFillType.AccountCreationUsername)) { + if (this.focusedFieldMatchesFillType(InlineMenuFillTypes.AccountCreationUsername)) { return true; } @@ -1149,7 +1152,7 @@ export class OverlayBackground implements OverlayBackgroundInterface { } let pageDetails = Array.from(pageDetailsForTab.values()); - if (this.focusedFieldMatchesFillType(InlineMenuFillType.CurrentPasswordUpdate)) { + if (this.focusedFieldMatchesFillType(InlineMenuFillTypes.CurrentPasswordUpdate)) { pageDetails = this.getFilteredPageDetails( pageDetails, this.inlineMenuFieldQualificationService.isUpdateCurrentPasswordField, @@ -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, @@ -1697,7 +1705,7 @@ export class OverlayBackground implements OverlayBackgroundInterface { private shouldUpdatePasswordGeneratorMenuOnFieldFocus() { return ( this.isInlineMenuButtonVisible && - this.focusedFieldMatchesFillType(InlineMenuFillType.PasswordGeneration) + this.focusedFieldMatchesFillType(InlineMenuFillTypes.PasswordGeneration) ); } @@ -1759,9 +1767,9 @@ export class OverlayBackground implements OverlayBackgroundInterface { private shouldUpdateAccountCreationMenuOnFieldFocus(previousFocusedFieldData: FocusedFieldData) { const accountCreationFieldBlurred = this.focusedFieldMatchesFillType( - InlineMenuFillType.AccountCreationUsername, + InlineMenuFillTypes.AccountCreationUsername, previousFocusedFieldData, - ) && !this.focusedFieldMatchesFillType(InlineMenuFillType.AccountCreationUsername); + ) && !this.focusedFieldMatchesFillType(InlineMenuFillTypes.AccountCreationUsername); return accountCreationFieldBlurred || this.shouldShowInlineMenuAccountCreation(); } @@ -1868,7 +1876,7 @@ export class OverlayBackground implements OverlayBackgroundInterface { return ( (this.shouldShowInlineMenuAccountCreation() || - this.focusedFieldMatchesFillType(InlineMenuFillType.PasswordGeneration)) && + this.focusedFieldMatchesFillType(InlineMenuFillTypes.PasswordGeneration)) && !!(loginData.password || loginData.newPassword) ); } @@ -3028,7 +3036,7 @@ export class OverlayBackground implements OverlayBackgroundInterface { } const focusFieldShouldShowPasswordGenerator = - this.focusedFieldMatchesFillType(InlineMenuFillType.PasswordGeneration) || + this.focusedFieldMatchesFillType(InlineMenuFillTypes.PasswordGeneration) || (showInlineMenuAccountCreation && this.focusedFieldMatchesAccountCreationType(InlineMenuAccountCreationFieldType.Password)); if (!focusFieldShouldShowPasswordGenerator) { diff --git a/apps/browser/src/autofill/browser/cipher-context-menu-handler.ts b/apps/browser/src/autofill/browser/cipher-context-menu-handler.ts index e2bf75350a2..b1d65fdea92 100644 --- a/apps/browser/src/autofill/browser/cipher-context-menu-handler.ts +++ b/apps/browser/src/autofill/browser/cipher-context-menu-handler.ts @@ -97,7 +97,9 @@ export class CipherContextMenuHandler { private async updateForCipher(cipher: CipherView) { if ( cipher == null || - !new Set([CipherType.Login, CipherType.Card, CipherType.Identity]).has(cipher.type) + !new Set([CipherType.Login, CipherType.Card, CipherType.Identity] as CipherType[]).has( + cipher.type, + ) ) { return; } diff --git a/apps/browser/src/autofill/content/components/.lit-storybook/main.ts b/apps/browser/src/autofill/content/components/.lit-storybook/main.ts index 9068bbfc27d..a316d8f5baa 100644 --- a/apps/browser/src/autofill/content/components/.lit-storybook/main.ts +++ b/apps/browser/src/autofill/content/components/.lit-storybook/main.ts @@ -58,6 +58,10 @@ const config: StorybookConfig = { }, ], }); + config.module.rules.push({ + test: /\.scss$/, + use: [require.resolve("css-loader"), require.resolve("sass-loader")], + }); } return config; }, diff --git a/apps/browser/src/autofill/content/components/buttons/action-button.ts b/apps/browser/src/autofill/content/components/buttons/action-button.ts index 881b44b4785..339b628875c 100644 --- a/apps/browser/src/autofill/content/components/buttons/action-button.ts +++ b/apps/browser/src/autofill/content/components/buttons/action-button.ts @@ -4,50 +4,70 @@ import { html, TemplateResult } from "lit"; import { Theme } from "@bitwarden/common/platform/enums"; import { border, themes, typography, spacing } from "../constants/styles"; +import { Spinner } from "../icons"; + +export type ActionButtonProps = { + buttonText: string | TemplateResult; + disabled?: boolean; + isLoading?: boolean; + theme: Theme; + handleClick: (e: Event) => void; + fullWidth?: boolean; +}; export function ActionButton({ buttonText, disabled = false, + isLoading = false, theme, handleClick, -}: { - buttonText: string | TemplateResult; - disabled?: boolean; - theme: Theme; - handleClick: (e: Event) => void; -}) { + fullWidth = true, +}: ActionButtonProps) { const handleButtonClick = (event: Event) => { - if (!disabled) { + if (!disabled && !isLoading) { handleClick(event); } }; return html` `; } -const actionButtonStyles = ({ disabled, theme }: { disabled: boolean; theme: Theme }) => css` +const actionButtonStyles = ({ + disabled, + fullWidth, + isLoading, + theme, +}: { + disabled: boolean; + fullWidth: boolean; + isLoading: boolean; + theme: Theme; +}) => css` ${typography.body2} user-select: none; + display: flex; + align-items: center; + justify-content: center; border: 1px solid transparent; border-radius: ${border.radius.full}; padding: ${spacing["1"]} ${spacing["3"]}; - width: 100%; + width: ${fullWidth ? "100%" : "auto"}; overflow: hidden; text-align: center; text-overflow: ellipsis; font-weight: 700; - ${disabled + ${disabled || isLoading ? ` background-color: ${themes[theme].secondary["300"]}; color: ${themes[theme].text.muted}; @@ -62,10 +82,15 @@ const actionButtonStyles = ({ disabled, theme }: { disabled: boolean; theme: The background-color: ${themes[theme].primary["700"]}; color: ${themes[theme].text.contrast}; } + :focus { + outline: 2px solid ${themes[theme].primary["600"]}; + outline-offset: 1px; + } `} svg { - width: fit-content; + padding: 2px 0; /* Match line-height of button body2 typography */ + width: auto; height: 16px; } `; diff --git a/apps/browser/src/autofill/content/components/buttons/additional-tasks/button-content.ts b/apps/browser/src/autofill/content/components/buttons/additional-tasks/button-content.ts new file mode 100644 index 00000000000..2357da4e785 --- /dev/null +++ b/apps/browser/src/autofill/content/components/buttons/additional-tasks/button-content.ts @@ -0,0 +1,29 @@ +import { css } from "@emotion/css"; +import { html } from "lit"; + +import { Theme } from "@bitwarden/common/platform/enums"; + +import { spacing, themes } from "../../constants/styles"; +import { ExternalLink } from "../../icons"; + +export function AdditionalTasksButtonContent({ + buttonText, + theme, +}: { + buttonText: string; + theme: Theme; +}) { + return html` +
+ ${buttonText} + ${ExternalLink({ theme, color: themes[theme].text.contrast })} +
+ `; +} + +export const additionalTasksButtonContentStyles = ({ theme }: { theme: Theme }) => css` + gap: ${spacing[2]}; + display: flex; + align-items: center; + white-space: nowrap; +`; diff --git a/apps/browser/src/autofill/content/components/buttons/badge-button.ts b/apps/browser/src/autofill/content/components/buttons/badge-button.ts index 3b3b84f8166..3cdd453ee1a 100644 --- a/apps/browser/src/autofill/content/components/buttons/badge-button.ts +++ b/apps/browser/src/autofill/content/components/buttons/badge-button.ts @@ -5,17 +5,23 @@ import { Theme } from "@bitwarden/common/platform/enums"; import { border, themes, typography, spacing } from "../constants/styles"; +export type BadgeButtonProps = { + buttonAction: (e: Event) => void; + buttonText: string; + itemName: string; + disabled?: boolean; + theme: Theme; + username?: string; +}; + export function BadgeButton({ buttonAction, buttonText, disabled = false, + itemName, theme, -}: { - buttonAction: (e: Event) => void; - buttonText: string; - disabled?: boolean; - theme: Theme; -}) { + username, +}: BadgeButtonProps) { const handleButtonClick = (event: Event) => { if (!disabled) { buttonAction(event); @@ -26,6 +32,7 @@ export function BadgeButton({ `; 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..ecbb736bb8e 100644 --- a/apps/browser/src/autofill/content/components/buttons/edit-button.ts +++ b/apps/browser/src/autofill/content/components/buttons/edit-button.ts @@ -6,21 +6,19 @@ import { Theme } from "@bitwarden/common/platform/enums"; import { themes, typography, spacing } from "../constants/styles"; import { PencilSquare } from "../icons"; -export function EditButton({ - buttonAction, - buttonText, - disabled = false, - theme, -}: { +export type EditButtonProps = { buttonAction: (e: Event) => void; buttonText: string; disabled?: boolean; theme: Theme; -}) { +}; + +export function EditButton({ buttonAction, buttonText, disabled = false, theme }: EditButtonProps) { return html`
@@ -88,30 +73,15 @@ exports[`AutofillInlineMenuList initAutofillInlineMenuList the inline menu with
@@ -139,30 +109,15 @@ exports[`AutofillInlineMenuList initAutofillInlineMenuList the inline menu with @@ -190,30 +145,15 @@ exports[`AutofillInlineMenuList initAutofillInlineMenuList the inline menu with @@ -248,13 +188,19 @@ exports[`AutofillInlineMenuList initAutofillInlineMenuList the list of ciphers f aria-hidden="true" fill="none" height="24" - viewBox="0 0 23 24" - width="23" + viewBox="0 0 24 24" + width="24" xmlns="http://www.w3.org/2000/svg" > + @@ -283,29 +229,23 @@ exports[`AutofillInlineMenuList initAutofillInlineMenuList the list of ciphers f @@ -323,30 +263,15 @@ exports[`AutofillInlineMenuList initAutofillInlineMenuList the list of ciphers f @@ -404,29 +329,23 @@ exports[`AutofillInlineMenuList initAutofillInlineMenuList the list of ciphers f @@ -473,29 +392,23 @@ exports[`AutofillInlineMenuList initAutofillInlineMenuList the list of ciphers f @@ -528,29 +441,23 @@ exports[`AutofillInlineMenuList initAutofillInlineMenuList the list of ciphers f @@ -575,15 +482,15 @@ exports[`AutofillInlineMenuList initAutofillInlineMenuList the list of ciphers f @@ -613,29 +520,23 @@ exports[`AutofillInlineMenuList initAutofillInlineMenuList the list of ciphers f @@ -683,29 +584,23 @@ exports[`AutofillInlineMenuList initAutofillInlineMenuList the list of ciphers f @@ -753,29 +648,23 @@ exports[`AutofillInlineMenuList initAutofillInlineMenuList the list of ciphers f @@ -876,29 +765,23 @@ exports[`AutofillInlineMenuList initAutofillInlineMenuList the list of ciphers f @@ -958,29 +841,23 @@ exports[`AutofillInlineMenuList initAutofillInlineMenuList the list of ciphers f @@ -1027,29 +904,23 @@ exports[`AutofillInlineMenuList initAutofillInlineMenuList the list of ciphers f @@ -1082,29 +953,23 @@ exports[`AutofillInlineMenuList initAutofillInlineMenuList the list of ciphers f @@ -1129,15 +994,15 @@ exports[`AutofillInlineMenuList initAutofillInlineMenuList the list of ciphers f @@ -1167,29 +1032,23 @@ exports[`AutofillInlineMenuList initAutofillInlineMenuList the list of ciphers f @@ -1237,29 +1096,23 @@ exports[`AutofillInlineMenuList initAutofillInlineMenuList the list of ciphers f @@ -1307,29 +1160,23 @@ exports[`AutofillInlineMenuList initAutofillInlineMenuList the list of ciphers f @@ -1389,29 +1236,23 @@ exports[`AutofillInlineMenuList initAutofillInlineMenuList the list of ciphers f @@ -1458,29 +1299,23 @@ exports[`AutofillInlineMenuList initAutofillInlineMenuList the list of ciphers f @@ -1513,29 +1348,23 @@ exports[`AutofillInlineMenuList initAutofillInlineMenuList the list of ciphers f @@ -1560,15 +1389,15 @@ exports[`AutofillInlineMenuList initAutofillInlineMenuList the list of ciphers f @@ -1598,29 +1427,23 @@ exports[`AutofillInlineMenuList initAutofillInlineMenuList the list of ciphers f @@ -1668,29 +1491,23 @@ exports[`AutofillInlineMenuList initAutofillInlineMenuList the list of ciphers f @@ -1738,29 +1555,23 @@ exports[`AutofillInlineMenuList initAutofillInlineMenuList the list of ciphers f @@ -1814,19 +1625,15 @@ exports[`AutofillInlineMenuList initAutofillInlineMenuList the list of ciphers f @@ -1842,29 +1649,23 @@ exports[`AutofillInlineMenuList initAutofillInlineMenuList the list of ciphers f @@ -1903,19 +1704,15 @@ exports[`AutofillInlineMenuList initAutofillInlineMenuList the list of ciphers f @@ -1931,29 +1728,23 @@ exports[`AutofillInlineMenuList initAutofillInlineMenuList the list of ciphers f @@ -1992,19 +1783,15 @@ exports[`AutofillInlineMenuList initAutofillInlineMenuList the list of ciphers f @@ -2026,29 +1813,23 @@ exports[`AutofillInlineMenuList initAutofillInlineMenuList the list of ciphers f @@ -2099,29 +1880,23 @@ exports[`AutofillInlineMenuList initAutofillInlineMenuList the list of ciphers f @@ -2169,29 +1944,23 @@ exports[`AutofillInlineMenuList initAutofillInlineMenuList the list of ciphers f @@ -2239,29 +2008,23 @@ exports[`AutofillInlineMenuList initAutofillInlineMenuList the list of ciphers f @@ -2287,10 +2050,10 @@ exports[`AutofillInlineMenuList initAutofillInlineMenuList the list of ciphers f > @@ -2407,29 +2170,23 @@ exports[`AutofillInlineMenuList initAutofillInlineMenuList the list of ciphers f @@ -2525,29 +2282,23 @@ exports[`AutofillInlineMenuList initAutofillInlineMenuList the list of ciphers f @@ -2578,29 +2329,21 @@ exports[`AutofillInlineMenuList initAutofillInlineMenuList the locked inline men unlockAccount @@ -2623,17 +2366,22 @@ exports[`AutofillInlineMenuList initAutofillInlineMenuList the password generato diff --git a/apps/browser/src/autofill/popup/fido2/fido2-cipher-row.component.ts b/apps/browser/src/autofill/popup/fido2/fido2-cipher-row.component.ts index 98e73d2174c..074e23d642d 100644 --- a/apps/browser/src/autofill/popup/fido2/fido2-cipher-row.component.ts +++ b/apps/browser/src/autofill/popup/fido2/fido2-cipher-row.component.ts @@ -10,8 +10,6 @@ import { ButtonModule, IconButtonModule, ItemModule, - SectionComponent, - SectionHeaderComponent, TypographyModule, } from "@bitwarden/components"; @@ -19,7 +17,6 @@ import { selector: "app-fido2-cipher-row", templateUrl: "fido2-cipher-row.component.html", changeDetection: ChangeDetectionStrategy.OnPush, - standalone: true, imports: [ BadgeModule, ButtonModule, @@ -27,8 +24,6 @@ import { IconButtonModule, ItemModule, JslibModule, - SectionComponent, - SectionHeaderComponent, TypographyModule, ], }) diff --git a/apps/browser/src/autofill/popup/fido2/fido2-use-browser-link.component.ts b/apps/browser/src/autofill/popup/fido2/fido2-use-browser-link.component.ts index 91d97ac96dc..27fe88130de 100644 --- a/apps/browser/src/autofill/popup/fido2/fido2-use-browser-link.component.ts +++ b/apps/browser/src/autofill/popup/fido2/fido2-use-browser-link.component.ts @@ -20,7 +20,6 @@ import { BrowserFido2UserInterfaceSession } from "../../fido2/services/browser-f @Component({ selector: "app-fido2-use-browser-link", templateUrl: "fido2-use-browser-link.component.html", - standalone: true, imports: [A11yModule, CdkConnectedOverlay, CdkOverlayOrigin, CommonModule, JslibModule], animations: [ trigger("transformPanel", [ diff --git a/apps/browser/src/autofill/popup/fido2/fido2.component.html b/apps/browser/src/autofill/popup/fido2/fido2.component.html index 80ea6726cb9..8d8394641e9 100644 --- a/apps/browser/src/autofill/popup/fido2/fido2.component.html +++ b/apps/browser/src/autofill/popup/fido2/fido2.component.html @@ -14,7 +14,7 @@ (click)="addCipher()" slot="end" > - + {{ "new" | i18n }} diff --git a/apps/browser/src/autofill/popup/fido2/fido2.component.ts b/apps/browser/src/autofill/popup/fido2/fido2.component.ts index 0471d460fd5..3107b60f475 100644 --- a/apps/browser/src/autofill/popup/fido2/fido2.component.ts +++ b/apps/browser/src/autofill/popup/fido2/fido2.component.ts @@ -74,7 +74,6 @@ interface ViewData { @Component({ selector: "app-fido2", templateUrl: "fido2.component.html", - standalone: true, imports: [ ButtonModule, CommonModule, @@ -216,9 +215,7 @@ export class Fido2Component implements OnInit, OnDestroy { this.ciphers = await Promise.all( message.cipherIds.map(async (cipherId) => { const cipher = await this.cipherService.get(cipherId, activeUserId); - return cipher.decrypt( - await this.cipherService.getKeyForCipherKeyDecryption(cipher, activeUserId), - ); + return this.cipherService.decrypt(cipher, activeUserId); }), ); @@ -237,9 +234,7 @@ export class Fido2Component implements OnInit, OnDestroy { this.ciphers = await Promise.all( message.existingCipherIds.map(async (cipherId) => { const cipher = await this.cipherService.get(cipherId, activeUserId); - return cipher.decrypt( - await this.cipherService.getKeyForCipherKeyDecryption(cipher, activeUserId), - ); + return this.cipherService.decrypt(cipher, activeUserId); }), ); @@ -446,10 +441,10 @@ export class Fido2Component implements OnInit, OnDestroy { ); this.buildCipher(name, username); - const cipher = await this.cipherService.encrypt(this.cipher, activeUserId); + const encrypted = await this.cipherService.encrypt(this.cipher, activeUserId); try { - await this.cipherService.createWithServer(cipher); - this.cipher.id = cipher.id; + await this.cipherService.createWithServer(encrypted); + this.cipher.id = encrypted.cipher.id; } catch (e) { this.logService.error(e); } diff --git a/apps/browser/src/autofill/popup/settings/autofill.component.html b/apps/browser/src/autofill/popup/settings/autofill.component.html index 4fd85ddce33..264b04b039b 100644 --- a/apps/browser/src/autofill/popup/settings/autofill.component.html +++ b/apps/browser/src/autofill/popup/settings/autofill.component.html @@ -6,6 +6,16 @@
+
+ +

{{ "autofillSuggestionsSectionTitle" | i18n }}

diff --git a/apps/browser/src/autofill/popup/settings/autofill.component.ts b/apps/browser/src/autofill/popup/settings/autofill.component.ts index c30f150e71d..9e83c3fc2c5 100644 --- a/apps/browser/src/autofill/popup/settings/autofill.component.ts +++ b/apps/browser/src/autofill/popup/settings/autofill.component.ts @@ -4,16 +4,20 @@ import { CommonModule } from "@angular/common"; import { Component, DestroyRef, OnInit } from "@angular/core"; import { takeUntilDestroyed } from "@angular/core/rxjs-interop"; import { + FormBuilder, + FormControl, + FormGroup, FormsModule, ReactiveFormsModule, - FormBuilder, - FormGroup, - FormControl, } from "@angular/forms"; import { RouterModule } from "@angular/router"; -import { firstValueFrom } from "rxjs"; +import { filter, firstValueFrom, Observable, switchMap } from "rxjs"; import { JslibModule } from "@bitwarden/angular/jslib.module"; +import { NudgesService, NudgeType } from "@bitwarden/angular/vault"; +import { SpotlightComponent } from "@bitwarden/angular/vault/components/spotlight/spotlight.component"; +import { Account, AccountService } from "@bitwarden/common/auth/abstractions/account.service"; +import { getUserId } from "@bitwarden/common/auth/services/account.service"; import { AutofillOverlayVisibility, BrowserClientVendors, @@ -54,6 +58,7 @@ import { TypographyModule, } from "@bitwarden/components"; +import { AutofillBrowserSettingsService } from "../../../autofill/services/autofill-browser-settings.service"; import { BrowserApi } from "../../../platform/browser/browser-api"; import { PopOutComponent } from "../../../platform/popup/components/pop-out.component"; import { PopupHeaderComponent } from "../../../platform/popup/layout/popup-header.component"; @@ -61,7 +66,6 @@ import { PopupPageComponent } from "../../../platform/popup/layout/popup-page.co @Component({ templateUrl: "autofill.component.html", - standalone: true, imports: [ CardComponent, CheckboxModule, @@ -81,6 +85,7 @@ import { PopupPageComponent } from "../../../platform/popup/layout/popup-page.co SelectModule, TypographyModule, ReactiveFormsModule, + SpotlightComponent, ], }) export class AutofillComponent implements OnInit { @@ -100,6 +105,12 @@ export class AutofillComponent implements OnInit { protected browserClientIsUnknown: boolean; protected autofillOnPageLoadFromPolicy$ = this.autofillSettingsService.activateAutofillOnPageLoadFromPolicy$; + protected showSpotlightNudge$: Observable = this.accountService.activeAccount$.pipe( + filter((account): account is Account => account !== null), + switchMap((account) => + this.nudgesService.showNudgeSpotlight$(NudgeType.AutofillNudge, account.id), + ), + ); protected autofillOnPageLoadForm = new FormGroup({ autofillOnPageLoad: new FormControl(), @@ -142,6 +153,9 @@ export class AutofillComponent implements OnInit { private configService: ConfigService, private formBuilder: FormBuilder, private destroyRef: DestroyRef, + private nudgesService: NudgesService, + private accountService: AccountService, + private autofillBrowserSettingsService: AutofillBrowserSettingsService, ) { this.autofillOnPageLoadOptions = [ { name: this.i18nService.t("autoFillOnPageLoadYes"), value: true }, @@ -165,7 +179,7 @@ export class AutofillComponent implements OnInit { { name: i18nService.t("never"), value: UriMatchStrategy.Never }, ]; - this.browserClientVendor = this.getBrowserClientVendor(); + this.browserClientVendor = BrowserApi.getBrowserClientVendor(window); this.disablePasswordManagerURI = DisablePasswordManagerUris[this.browserClientVendor]; this.browserShortcutsURI = BrowserShortcutsUris[this.browserClientVendor]; this.browserClientIsUnknown = this.browserClientVendor === BrowserClientVendors.Unknown; @@ -173,7 +187,11 @@ export class AutofillComponent implements OnInit { async ngOnInit() { this.canOverrideBrowserAutofillSetting = !this.browserClientIsUnknown; - this.defaultBrowserAutofillDisabled = await this.browserAutofillSettingCurrentlyOverridden(); + + this.defaultBrowserAutofillDisabled = + await this.autofillBrowserSettingsService.isBrowserAutofillSettingOverridden( + this.browserClientVendor, + ); this.inlineMenuVisibility = await firstValueFrom( this.autofillSettingsService.inlineMenuVisibility$, @@ -308,6 +326,40 @@ export class AutofillComponent implements OnInit { ); } + get spotlightButtonIcon() { + if (this.browserClientVendor === BrowserClientVendors.Unknown) { + return "bwi-external-link"; + } + return null; + } + + get browserClientVendorExtended() { + if (this.browserClientVendor !== BrowserClientVendors.Unknown) { + return this.browserClientVendor; + } + if (this.platformUtilsService.isFirefox()) { + return "Firefox"; + } + if (this.platformUtilsService.isSafari()) { + return "Safari"; + } + return BrowserClientVendors.Unknown; + } + + get spotlightButtonText() { + if (this.browserClientVendorExtended === BrowserClientVendors.Unknown) { + return this.i18nService.t("turnOffAutofill"); + } + return this.i18nService.t("turnOffBrowserAutofill", this.browserClientVendorExtended); + } + + async dismissSpotlight() { + await this.nudgesService.dismissNudge( + NudgeType.AutofillNudge, + await firstValueFrom(this.accountService.activeAccount$.pipe(getUserId)), + ); + } + async updateInlineMenuVisibility() { if (!this.enableInlineMenu) { this.enableInlineMenuOnIconSelect = false; @@ -346,26 +398,6 @@ export class AutofillComponent implements OnInit { } } - private getBrowserClientVendor(): BrowserClientVendor { - if (this.platformUtilsService.isChrome()) { - return BrowserClientVendors.Chrome; - } - - if (this.platformUtilsService.isOpera()) { - return BrowserClientVendors.Opera; - } - - if (this.platformUtilsService.isEdge()) { - return BrowserClientVendors.Edge; - } - - if (this.platformUtilsService.isVivaldi()) { - return BrowserClientVendors.Vivaldi; - } - - return BrowserClientVendors.Unknown; - } - protected async openURI(event: Event, uri: BrowserShortcutsUri | DisablePasswordManagerUri) { event.preventDefault(); @@ -422,7 +454,7 @@ export class AutofillComponent implements OnInit { if ( this.inlineMenuVisibility === AutofillOverlayVisibility.Off || !this.canOverrideBrowserAutofillSetting || - (await this.browserAutofillSettingCurrentlyOverridden()) + this.defaultBrowserAutofillDisabled ) { return; } @@ -460,6 +492,9 @@ export class AutofillComponent implements OnInit { } await BrowserApi.updateDefaultBrowserAutofillSettings(!this.defaultBrowserAutofillDisabled); + this.autofillBrowserSettingsService.setDefaultBrowserAutofillDisabled( + this.defaultBrowserAutofillDisabled, + ); } private handleOverrideDialogAccept = async () => { @@ -467,18 +502,6 @@ export class AutofillComponent implements OnInit { await this.updateDefaultBrowserAutofillDisabled(); }; - async browserAutofillSettingCurrentlyOverridden() { - if (!this.canOverrideBrowserAutofillSetting) { - return false; - } - - if (!(await this.privacyPermissionGranted())) { - return false; - } - - return await BrowserApi.browserAutofillSettingsOverridden(); - } - async privacyPermissionGranted(): Promise { return await BrowserApi.permissionsGranted(["privacy"]); } diff --git a/apps/browser/src/autofill/popup/settings/blocked-domains.component.ts b/apps/browser/src/autofill/popup/settings/blocked-domains.component.ts index c59ce24c7c4..15379eff436 100644 --- a/apps/browser/src/autofill/popup/settings/blocked-domains.component.ts +++ b/apps/browser/src/autofill/popup/settings/blocked-domains.component.ts @@ -44,7 +44,6 @@ import { PopupRouterCacheService } from "../../../platform/popup/view-cache/popu @Component({ selector: "app-blocked-domains", templateUrl: "blocked-domains.component.html", - standalone: true, imports: [ ButtonModule, CardComponent, diff --git a/apps/browser/src/autofill/popup/settings/excluded-domains.component.ts b/apps/browser/src/autofill/popup/settings/excluded-domains.component.ts index 504d2dbfc17..a5bfad726f5 100644 --- a/apps/browser/src/autofill/popup/settings/excluded-domains.component.ts +++ b/apps/browser/src/autofill/popup/settings/excluded-domains.component.ts @@ -45,7 +45,6 @@ import { PopupRouterCacheService } from "../../../platform/popup/view-cache/popu @Component({ selector: "app-excluded-domains", templateUrl: "excluded-domains.component.html", - standalone: true, imports: [ ButtonModule, CardComponent, diff --git a/apps/browser/src/autofill/popup/settings/notifications.component.ts b/apps/browser/src/autofill/popup/settings/notifications.component.ts index be447e3f885..cb10dec620b 100644 --- a/apps/browser/src/autofill/popup/settings/notifications.component.ts +++ b/apps/browser/src/autofill/popup/settings/notifications.component.ts @@ -18,20 +18,17 @@ import { } from "@bitwarden/components"; import { PopOutComponent } from "../../../platform/popup/components/pop-out.component"; -import { PopupFooterComponent } from "../../../platform/popup/layout/popup-footer.component"; import { PopupHeaderComponent } from "../../../platform/popup/layout/popup-header.component"; import { PopupPageComponent } from "../../../platform/popup/layout/popup-page.component"; @Component({ templateUrl: "notifications.component.html", - standalone: true, imports: [ CommonModule, JslibModule, RouterModule, PopupPageComponent, PopupHeaderComponent, - PopupFooterComponent, PopOutComponent, ItemModule, CardComponent, diff --git a/apps/browser/src/autofill/services/abstractions/autofill.service.ts b/apps/browser/src/autofill/services/abstractions/autofill.service.ts index 5b1b4b3b8bb..daafd871789 100644 --- a/apps/browser/src/autofill/services/abstractions/autofill.service.ts +++ b/apps/browser/src/autofill/services/abstractions/autofill.service.ts @@ -87,5 +87,9 @@ export abstract class AutofillService { cipherType?: CipherType, ) => Promise; setAutoFillOnPageLoadOrgPolicy: () => Promise; - isPasswordRepromptRequired: (cipher: CipherView, tab: chrome.tabs.Tab) => Promise; + isPasswordRepromptRequired: ( + cipher: CipherView, + tab: chrome.tabs.Tab, + action?: string, + ) => Promise; } diff --git a/apps/browser/src/autofill/services/autofill-browser-settings.service.ts b/apps/browser/src/autofill/services/autofill-browser-settings.service.ts new file mode 100644 index 00000000000..ba59a655b77 --- /dev/null +++ b/apps/browser/src/autofill/services/autofill-browser-settings.service.ts @@ -0,0 +1,31 @@ +import { Injectable } from "@angular/core"; +import { BehaviorSubject, Observable } from "rxjs"; + +import { BrowserClientVendors } from "@bitwarden/common/autofill/constants"; +import { BrowserClientVendor } from "@bitwarden/common/autofill/types"; + +import { BrowserApi } from "../../platform/browser/browser-api"; + +/** + * Service class for various Autofill-related browser API operations. + */ +@Injectable({ + providedIn: "root", +}) +export class AutofillBrowserSettingsService { + async isBrowserAutofillSettingOverridden(browserClient: BrowserClientVendor) { + return ( + browserClient !== BrowserClientVendors.Unknown && + (await BrowserApi.browserAutofillSettingsOverridden()) + ); + } + + private _defaultBrowserAutofillDisabled$ = new BehaviorSubject(false); + + defaultBrowserAutofillDisabled$: Observable = + this._defaultBrowserAutofillDisabled$.asObservable(); + + setDefaultBrowserAutofillDisabled(value: boolean) { + this._defaultBrowserAutofillDisabled$.next(value); + } +} diff --git a/apps/browser/src/autofill/services/autofill-overlay-content.service.spec.ts b/apps/browser/src/autofill/services/autofill-overlay-content.service.spec.ts index 48bc7ceafda..730b002953b 100644 --- a/apps/browser/src/autofill/services/autofill-overlay-content.service.spec.ts +++ b/apps/browser/src/autofill/services/autofill-overlay-content.service.spec.ts @@ -6,7 +6,7 @@ import { CipherType } from "@bitwarden/common/vault/enums"; import AutofillInit from "../content/autofill-init"; import { AutofillOverlayElement, - InlineMenuFillType, + InlineMenuFillTypes, MAX_SUB_FRAME_DEPTH, RedirectFocusDirection, } from "../enums/autofill-overlay.enum"; @@ -1383,7 +1383,7 @@ describe("AutofillOverlayContentService", () => { ); expect(autofillFieldElement.removeEventListener).toHaveBeenCalled(); expect(inputAccountFieldData.inlineMenuFillType).toEqual( - InlineMenuFillType.AccountCreationUsername, + InlineMenuFillTypes.AccountCreationUsername, ); }); @@ -1420,7 +1420,7 @@ describe("AutofillOverlayContentService", () => { await flushPromises(); expect(currentPasswordFieldData.inlineMenuFillType).toEqual( - InlineMenuFillType.CurrentPasswordUpdate, + InlineMenuFillTypes.CurrentPasswordUpdate, ); }); }); 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..1a972e0eaa0 100644 --- a/apps/browser/src/autofill/services/autofill-overlay-content.service.ts +++ b/apps/browser/src/autofill/services/autofill-overlay-content.service.ts @@ -24,7 +24,7 @@ import { AutofillFieldQualifier, AutofillFieldQualifierType } from "../enums/aut import { AutofillOverlayElement, InlineMenuAccountCreationFieldType, - InlineMenuFillType, + InlineMenuFillTypes, MAX_SUB_FRAME_DEPTH, RedirectFocusDirection, } from "../enums/autofill-overlay.enum"; @@ -789,11 +789,11 @@ export class AutofillOverlayContentService implements AutofillOverlayContentServ if (!autofillFieldData.fieldQualifier) { switch (autofillFieldData.inlineMenuFillType) { case CipherType.Login: - case InlineMenuFillType.CurrentPasswordUpdate: + case InlineMenuFillTypes.CurrentPasswordUpdate: this.qualifyUserFilledField(autofillFieldData, this.loginFieldQualifiers); break; - case InlineMenuFillType.AccountCreationUsername: - case InlineMenuFillType.PasswordGeneration: + case InlineMenuFillTypes.AccountCreationUsername: + case InlineMenuFillTypes.PasswordGeneration: this.qualifyUserFilledField(autofillFieldData, this.accountCreationFieldQualifiers); break; case CipherType.Card: @@ -1106,18 +1106,18 @@ export class AutofillOverlayContentService implements AutofillOverlayContentServ */ private setQualifiedAccountCreationFillType(autofillFieldData: AutofillField) { if (this.inlineMenuFieldQualificationService.isNewPasswordField(autofillFieldData)) { - autofillFieldData.inlineMenuFillType = InlineMenuFillType.PasswordGeneration; + autofillFieldData.inlineMenuFillType = InlineMenuFillTypes.PasswordGeneration; this.qualifyAccountCreationFieldType(autofillFieldData); return; } if (this.inlineMenuFieldQualificationService.isUpdateCurrentPasswordField(autofillFieldData)) { - autofillFieldData.inlineMenuFillType = InlineMenuFillType.CurrentPasswordUpdate; + autofillFieldData.inlineMenuFillType = InlineMenuFillTypes.CurrentPasswordUpdate; return; } if (this.inlineMenuFieldQualificationService.isUsernameField(autofillFieldData)) { - autofillFieldData.inlineMenuFillType = InlineMenuFillType.AccountCreationUsername; + autofillFieldData.inlineMenuFillType = InlineMenuFillTypes.AccountCreationUsername; this.qualifyAccountCreationFieldType(autofillFieldData); } } @@ -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..fdd881c2760 100644 --- a/apps/browser/src/autofill/services/autofill.service.ts +++ b/apps/browser/src/autofill/services/autofill.service.ts @@ -593,15 +593,20 @@ export default class AutofillService implements AutofillServiceInterface { * * @param cipher - The cipher to autofill * @param tab - The tab to autofill + * @param action - override for default action once reprompt is completed successfully */ - async isPasswordRepromptRequired(cipher: CipherView, tab: chrome.tabs.Tab): Promise { + async isPasswordRepromptRequired( + cipher: CipherView, + tab: chrome.tabs.Tab, + action?: string, + ): Promise { const userHasMasterPasswordAndKeyHash = await this.userVerificationService.hasMasterPasswordAndMasterKeyHash(); if (cipher.reprompt === CipherRepromptType.Password && userHasMasterPasswordAndKeyHash) { if (!this.isDebouncingPasswordRepromptPopout()) { await this.openVaultItemPasswordRepromptPopout(tab, { cipherId: cipher.id, - action: "autofill", + action: action ?? "autofill", }); } @@ -931,28 +936,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; } }); } @@ -1565,252 +1579,6 @@ export default class AutofillService implements AutofillServiceInterface { return [expectedDateFormat, dateFormatPatterns]; } - /** - * Generates the autofill script for the specified page details and identify cipher item. - * @param {AutofillScript} fillScript - * @param {AutofillPageDetails} pageDetails - * @param {{[p: string]: AutofillField}} filledFields - * @param {GenerateFillScriptOptions} options - * @returns {AutofillScript} - * @private - */ - private async generateIdentityFillScript( - fillScript: AutofillScript, - pageDetails: AutofillPageDetails, - filledFields: { [id: string]: AutofillField }, - options: GenerateFillScriptOptions, - ): Promise { - if (await this.configService.getFeatureFlag(FeatureFlag.GenerateIdentityFillScriptRefactor)) { - return this._generateIdentityFillScript(fillScript, pageDetails, filledFields, options); - } - - if (!options.cipher.identity) { - return null; - } - - const fillFields: { [id: string]: AutofillField } = {}; - - pageDetails.fields.forEach((f) => { - if ( - AutofillService.isExcludedFieldType(f, AutoFillConstants.ExcludedAutofillTypes) || - ["current-password", "new-password"].includes(f.autoCompleteType) - ) { - return; - } - - for (let i = 0; i < IdentityAutoFillConstants.IdentityAttributes.length; i++) { - const attr = IdentityAutoFillConstants.IdentityAttributes[i]; - // eslint-disable-next-line - if (!f.hasOwnProperty(attr) || !f[attr] || !f.viewable) { - continue; - } - - // ref https://html.spec.whatwg.org/multipage/form-control-infrastructure.html#autofill - // ref https://developers.google.com/web/fundamentals/design-and-ux/input/forms/ - if ( - !fillFields.name && - AutofillService.isFieldMatch( - f[attr], - IdentityAutoFillConstants.FullNameFieldNames, - IdentityAutoFillConstants.FullNameFieldNameValues, - ) - ) { - fillFields.name = f; - break; - } else if ( - !fillFields.firstName && - AutofillService.isFieldMatch(f[attr], IdentityAutoFillConstants.FirstnameFieldNames) - ) { - fillFields.firstName = f; - break; - } else if ( - !fillFields.middleName && - AutofillService.isFieldMatch(f[attr], IdentityAutoFillConstants.MiddlenameFieldNames) - ) { - fillFields.middleName = f; - break; - } else if ( - !fillFields.lastName && - AutofillService.isFieldMatch(f[attr], IdentityAutoFillConstants.LastnameFieldNames) - ) { - fillFields.lastName = f; - break; - } else if ( - !fillFields.title && - AutofillService.isFieldMatch(f[attr], IdentityAutoFillConstants.TitleFieldNames) - ) { - fillFields.title = f; - break; - } else if ( - !fillFields.email && - AutofillService.isFieldMatch(f[attr], IdentityAutoFillConstants.EmailFieldNames) - ) { - fillFields.email = f; - break; - } else if ( - !fillFields.address1 && - AutofillService.isFieldMatch(f[attr], IdentityAutoFillConstants.Address1FieldNames) - ) { - fillFields.address1 = f; - break; - } else if ( - !fillFields.address2 && - AutofillService.isFieldMatch(f[attr], IdentityAutoFillConstants.Address2FieldNames) - ) { - fillFields.address2 = f; - break; - } else if ( - !fillFields.address3 && - AutofillService.isFieldMatch(f[attr], IdentityAutoFillConstants.Address3FieldNames) - ) { - fillFields.address3 = f; - break; - } else if ( - !fillFields.address && - AutofillService.isFieldMatch( - f[attr], - IdentityAutoFillConstants.AddressFieldNames, - IdentityAutoFillConstants.AddressFieldNameValues, - ) - ) { - fillFields.address = f; - break; - } else if ( - !fillFields.postalCode && - AutofillService.isFieldMatch(f[attr], IdentityAutoFillConstants.PostalCodeFieldNames) - ) { - fillFields.postalCode = f; - break; - } else if ( - !fillFields.city && - AutofillService.isFieldMatch(f[attr], IdentityAutoFillConstants.CityFieldNames) - ) { - fillFields.city = f; - break; - } else if ( - !fillFields.state && - AutofillService.isFieldMatch(f[attr], IdentityAutoFillConstants.StateFieldNames) - ) { - fillFields.state = f; - break; - } else if ( - !fillFields.country && - AutofillService.isFieldMatch(f[attr], IdentityAutoFillConstants.CountryFieldNames) - ) { - fillFields.country = f; - break; - } else if ( - !fillFields.phone && - AutofillService.isFieldMatch(f[attr], IdentityAutoFillConstants.PhoneFieldNames) - ) { - fillFields.phone = f; - break; - } else if ( - !fillFields.username && - AutofillService.isFieldMatch(f[attr], IdentityAutoFillConstants.UserNameFieldNames) - ) { - fillFields.username = f; - break; - } else if ( - !fillFields.company && - AutofillService.isFieldMatch(f[attr], IdentityAutoFillConstants.CompanyFieldNames) - ) { - fillFields.company = f; - break; - } - } - }); - - const identity = options.cipher.identity; - this.makeScriptAction(fillScript, identity, fillFields, filledFields, "title"); - this.makeScriptAction(fillScript, identity, fillFields, filledFields, "firstName"); - this.makeScriptAction(fillScript, identity, fillFields, filledFields, "middleName"); - this.makeScriptAction(fillScript, identity, fillFields, filledFields, "lastName"); - this.makeScriptAction(fillScript, identity, fillFields, filledFields, "address1"); - this.makeScriptAction(fillScript, identity, fillFields, filledFields, "address2"); - this.makeScriptAction(fillScript, identity, fillFields, filledFields, "address3"); - this.makeScriptAction(fillScript, identity, fillFields, filledFields, "city"); - this.makeScriptAction(fillScript, identity, fillFields, filledFields, "postalCode"); - this.makeScriptAction(fillScript, identity, fillFields, filledFields, "company"); - this.makeScriptAction(fillScript, identity, fillFields, filledFields, "email"); - this.makeScriptAction(fillScript, identity, fillFields, filledFields, "phone"); - this.makeScriptAction(fillScript, identity, fillFields, filledFields, "username"); - - let filledState = false; - if (fillFields.state && identity.state && identity.state.length > 2) { - const stateLower = identity.state.toLowerCase(); - const isoState = - IdentityAutoFillConstants.IsoStates[stateLower] || - IdentityAutoFillConstants.IsoProvinces[stateLower]; - if (isoState) { - filledState = true; - this.makeScriptActionWithValue(fillScript, isoState, fillFields.state, filledFields); - } - } - - if (!filledState) { - this.makeScriptAction(fillScript, identity, fillFields, filledFields, "state"); - } - - let filledCountry = false; - if (fillFields.country && identity.country && identity.country.length > 2) { - const countryLower = identity.country.toLowerCase(); - const isoCountry = IdentityAutoFillConstants.IsoCountries[countryLower]; - if (isoCountry) { - filledCountry = true; - this.makeScriptActionWithValue(fillScript, isoCountry, fillFields.country, filledFields); - } - } - - if (!filledCountry) { - this.makeScriptAction(fillScript, identity, fillFields, filledFields, "country"); - } - - if (fillFields.name && (identity.firstName || identity.lastName)) { - let fullName = ""; - if (AutofillService.hasValue(identity.firstName)) { - fullName = identity.firstName; - } - if (AutofillService.hasValue(identity.middleName)) { - if (fullName !== "") { - fullName += " "; - } - fullName += identity.middleName; - } - if (AutofillService.hasValue(identity.lastName)) { - if (fullName !== "") { - fullName += " "; - } - fullName += identity.lastName; - } - - this.makeScriptActionWithValue(fillScript, fullName, fillFields.name, filledFields); - } - - if (fillFields.address && AutofillService.hasValue(identity.address1)) { - let address = ""; - if (AutofillService.hasValue(identity.address1)) { - address = identity.address1; - } - if (AutofillService.hasValue(identity.address2)) { - if (address !== "") { - address += ", "; - } - address += identity.address2; - } - if (AutofillService.hasValue(identity.address3)) { - if (address !== "") { - address += ", "; - } - address += identity.address3; - } - - this.makeScriptActionWithValue(fillScript, address, fillFields.address, filledFields); - } - - return fillScript; - } - /** * Generates the autofill script for the specified page details and identity cipher item. * @@ -1819,7 +1587,7 @@ export default class AutofillService implements AutofillServiceInterface { * @param filledFields - The fields that have already been filled, passed between method references * @param options - Contains data used to fill cipher items */ - private _generateIdentityFillScript( + private generateIdentityFillScript( fillScript: AutofillScript, pageDetails: AutofillPageDetails, filledFields: { [id: string]: AutofillField }, @@ -2903,52 +2671,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/services/notification-change-login-password.service.ts b/apps/browser/src/autofill/services/notification-change-login-password.service.ts new file mode 100644 index 00000000000..f7b8c2cfb9c --- /dev/null +++ b/apps/browser/src/autofill/services/notification-change-login-password.service.ts @@ -0,0 +1,99 @@ +import { Utils } from "@bitwarden/common/platform/misc/utils"; +import { CipherType } from "@bitwarden/common/vault/enums"; +import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view"; + +// Duplicates Default Change Login Password Service, for now +// Since the former is an Angular injectable service, and we +// need to use the function inside of lit components. +// If primary service can be abstracted, that would be ideal. + +export class TemporaryNotificationChangeLoginService { + async getChangePasswordUrl(cipher: CipherView, fallback = false): Promise { + // Ensure we have a cipher with at least one URI + if (cipher.type !== CipherType.Login || cipher.login == null || !cipher.login.hasUris) { + return null; + } + + // Filter for valid URLs that are HTTP(S) + const urls = cipher.login.uris + .map((m) => Utils.getUrl(m.uri)) + .filter((m) => m != null && (m.protocol === "http:" || m.protocol === "https:")); + + if (urls.length === 0) { + return null; + } + + for (const url of urls) { + const [reliable, wellKnownChangeUrl] = await Promise.all([ + this.hasReliableHttpStatusCode(url.origin), + this.getWellKnownChangePasswordUrl(url.origin), + ]); + + // Some servers return a 200 OK for a resource that should not exist + // Which means we cannot trust the well-known URL is valid, so we skip it + // to avoid potentially sending users to a 404 page + if (reliable && wellKnownChangeUrl != null) { + return wellKnownChangeUrl; + } + } + + // No reliable well-known URL found, fallback to the first URL + + // @TODO reimplement option in original service to indicate if no URL found. + // return urls[0].href; (originally) + return fallback ? urls[0].href : null; + } + + /** + * Checks if the server returns a non-200 status code for a resource that should not exist. + * See https://w3c.github.io/webappsec-change-password-url/response-code-reliability.html#semantics + * @param urlOrigin The origin of the URL to check + */ + private async hasReliableHttpStatusCode(urlOrigin: string): Promise { + try { + const url = new URL( + "./.well-known/resource-that-should-not-exist-whose-status-code-should-not-be-200", + urlOrigin, + ); + + const request = new Request(url, { + method: "GET", + mode: "same-origin", + credentials: "omit", + cache: "no-store", + redirect: "follow", + }); + + const response = await fetch(request); + return !response.ok; + } catch { + return false; + } + } + + /** + * Builds a well-known change password URL for the given origin. Attempts to fetch the URL to ensure a valid response + * is returned. Returns null if the request throws or the response is not 200 OK. + * See https://w3c.github.io/webappsec-change-password-url/ + * @param urlOrigin The origin of the URL to check + */ + private async getWellKnownChangePasswordUrl(urlOrigin: string): Promise { + try { + const url = new URL("./.well-known/change-password", urlOrigin); + + const request = new Request(url, { + method: "GET", + mode: "same-origin", + credentials: "omit", + cache: "no-store", + redirect: "follow", + }); + + const response = await fetch(request); + + return response.ok ? url.toString() : null; + } catch { + return null; + } + } +} diff --git a/apps/browser/src/autofill/shared/styles/variables.scss b/apps/browser/src/autofill/shared/styles/variables.scss index ae6a060798a..1e804ed8fd2 100644 --- a/apps/browser/src/autofill/shared/styles/variables.scss +++ b/apps/browser/src/autofill/shared/styles/variables.scss @@ -1,6 +1,6 @@ $dark-icon-themes: "theme_dark"; -$font-family-sans-serif: "DM Sans", "Helvetica Neue", Helvetica, Arial, sans-serif; +$font-family-sans-serif: Roboto, "Helvetica Neue", Helvetica, Arial, sans-serif; $font-family-source-code-pro: "Source Code Pro", monospace; $font-size-base: 14px; 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/autofill/types/index.ts b/apps/browser/src/autofill/types/index.ts index 30ebf38fef5..93bf35d1b3e 100644 --- a/apps/browser/src/autofill/types/index.ts +++ b/apps/browser/src/autofill/types/index.ts @@ -54,4 +54,7 @@ export type FormFieldElement = FillableFormFieldElement | HTMLSpanElement; export type FormElementWithAttribute = FormFieldElement & Record; -export type AutofillCipherTypeId = CipherType.Login | CipherType.Card | CipherType.Identity; +export type AutofillCipherTypeId = + | typeof CipherType.Login + | typeof CipherType.Card + | typeof CipherType.Identity; diff --git a/apps/browser/src/autofill/utils/svg-icons.ts b/apps/browser/src/autofill/utils/svg-icons.ts index b04d18608ec..343acc00b06 100644 --- a/apps/browser/src/autofill/utils/svg-icons.ts +++ b/apps/browser/src/autofill/utils/svg-icons.ts @@ -5,34 +5,34 @@ export const logoLockedIcon = ''; export const globeIcon = - ''; + ''; export const creditCardIcon = - ''; + ''; export const idCardIcon = - ''; + ''; export const lockIcon = - ''; + ''; export const plusIcon = - ''; + ''; export const viewCipherIcon = - ''; + ''; export const passkeyIcon = - ''; + ''; export const circleCheckIcon = - ''; + ''; export const spinnerIcon = - ''; + ''; export const keyIcon = - ''; + ''; export const refreshIcon = - ''; + ''; diff --git a/apps/browser/src/background/main.background.spec.ts b/apps/browser/src/background/main.background.spec.ts index c2cd38b7a30..83c4a9597ea 100644 --- a/apps/browser/src/background/main.background.spec.ts +++ b/apps/browser/src/background/main.background.spec.ts @@ -1,5 +1,5 @@ -// This test skips all the initilization of the background script and just -// focuses on making sure we don't accidently delete the initilization of +// This test skips all the initialization of the background script and just +// focuses on making sure we don't accidentally delete the initialization of // background vault syncing. This has happened before! describe("MainBackground sync task scheduling", () => { it("includes code to schedule the sync interval task", () => { diff --git a/apps/browser/src/background/main.background.ts b/apps/browser/src/background/main.background.ts index 2b6827aafa4..c353cdb4f93 100644 --- a/apps/browser/src/background/main.background.ts +++ b/apps/browser/src/background/main.background.ts @@ -39,7 +39,6 @@ import { TokenService as TokenServiceAbstraction } from "@bitwarden/common/auth/ import { UserVerificationApiServiceAbstraction } from "@bitwarden/common/auth/abstractions/user-verification/user-verification-api.service.abstraction"; import { UserVerificationService as UserVerificationServiceAbstraction } from "@bitwarden/common/auth/abstractions/user-verification/user-verification.service.abstraction"; import { AuthenticationStatus } from "@bitwarden/common/auth/enums/authentication-status"; -import { ForceSetPasswordReason } from "@bitwarden/common/auth/models/domain/force-set-password-reason"; import { AccountServiceImplementation } from "@bitwarden/common/auth/services/account.service"; import { AuthService } from "@bitwarden/common/auth/services/auth.service"; import { AvatarService } from "@bitwarden/common/auth/services/avatar.service"; @@ -184,6 +183,7 @@ import { SendStateProvider } from "@bitwarden/common/tools/send/services/send-st import { SendService } from "@bitwarden/common/tools/send/services/send.service"; import { InternalSendService as InternalSendServiceAbstraction } from "@bitwarden/common/tools/send/services/send.service.abstraction"; import { UserId } from "@bitwarden/common/types/guid"; +import { CipherEncryptionService } from "@bitwarden/common/vault/abstractions/cipher-encryption.service"; import { CipherService as CipherServiceAbstraction } from "@bitwarden/common/vault/abstractions/cipher.service"; import { CipherFileUploadService as CipherFileUploadServiceAbstraction } from "@bitwarden/common/vault/abstractions/file-upload/cipher-file-upload.service"; import { FolderApiServiceAbstraction } from "@bitwarden/common/vault/abstractions/folder/folder-api.service.abstraction"; @@ -191,11 +191,16 @@ 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, } from "@bitwarden/common/vault/services/cipher-authorization.service"; import { CipherService } from "@bitwarden/common/vault/services/cipher.service"; +import { DefaultCipherEncryptionService } from "@bitwarden/common/vault/services/default-cipher-encryption.service"; import { CipherFileUploadService } from "@bitwarden/common/vault/services/file-upload/cipher-file-upload.service"; import { FolderApiService } from "@bitwarden/common/vault/services/folder/folder-api.service"; import { FolderService } from "@bitwarden/common/vault/services/folder/folder.service"; @@ -402,8 +407,10 @@ export default class MainBackground { sdkService: SdkService; sdkLoadService: SdkLoadService; cipherAuthorizationService: CipherAuthorizationService; + endUserNotificationService: EndUserNotificationService; inlineMenuFieldQualificationService: InlineMenuFieldQualificationService; taskService: TaskService; + cipherEncryptionService: CipherEncryptionService; ipcContentScriptManagerService: IpcContentScriptManagerService; ipcService: IpcService; @@ -430,7 +437,7 @@ export default class MainBackground { constructor() { // Services - const lockedCallback = async (userId?: string) => { + const lockedCallback = async (userId: UserId) => { await this.refreshBadge(); await this.refreshMenu(true); if (this.systemService != null) { @@ -716,6 +723,7 @@ export default class MainBackground { this.logService, (logoutReason: LogoutReason, userId?: UserId) => this.logout(logoutReason, userId), this.vaultTimeoutSettingsService, + { createRequest: (url, request) => new Request(url, request) }, ); this.fileUploadService = new FileUploadService(this.logService, this.apiService); @@ -851,6 +859,11 @@ export default class MainBackground { this.bulkEncryptService = new FallbackBulkEncryptService(this.encryptService); + this.cipherEncryptionService = new DefaultCipherEncryptionService( + this.sdkService, + this.logService, + ); + this.cipherService = new CipherService( this.keyService, this.domainSettingsService, @@ -866,6 +879,7 @@ export default class MainBackground { this.stateProvider, this.accountService, this.logService, + this.cipherEncryptionService, ); this.folderService = new FolderService( this.keyService, @@ -1142,7 +1156,6 @@ export default class MainBackground { this.fido2ClientService, this.vaultSettingsService, this.scriptInjectorService, - this.configService, this.authService, ); @@ -1190,7 +1203,6 @@ export default class MainBackground { this.stateProvider, this.apiService, this.organizationService, - this.configService, this.authService, this.notificationsService, messageListener, @@ -1201,6 +1213,7 @@ export default class MainBackground { this.authService, this.autofillService, this.cipherService, + this.collectionService, this.configService, this.domainSettingsService, this.environmentService, @@ -1217,6 +1230,9 @@ export default class MainBackground { this.overlayNotificationsBackground = new OverlayNotificationsBackground( this.logService, this.notificationBackground, + this.taskService, + this.accountService, + this.cipherService, ); this.autoSubmitLoginBackground = new AutoSubmitLoginBackground( @@ -1224,7 +1240,6 @@ export default class MainBackground { this.autofillService, this.scriptInjectorService, this.authService, - this.configService, this.platformUtilsService, this.policyService, this.accountService, @@ -1313,13 +1328,20 @@ export default class MainBackground { this.collectionService, this.organizationService, this.accountService, - this.configService, ); this.inlineMenuFieldQualificationService = new InlineMenuFieldQualificationService(); 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() { @@ -1402,10 +1424,11 @@ export default class MainBackground { this.backgroundSyncService.init(); this.notificationsService.startListening(); - if (await this.configService.getFeatureFlag(FeatureFlag.SecurityTasks)) { - this.taskService.listenForTaskNotifications(); - } + this.taskService.listenForTaskNotifications(); + if (await this.configService.getFeatureFlag(FeatureFlag.EndUserNotifications)) { + this.endUserNotificationService.listenForEndUserNotifications(); + } resolve(); }, 500); }); @@ -1494,9 +1517,6 @@ export default class MainBackground { } nextAccountStatus = await this.authService.getAuthStatus(userId); - const forcePasswordReset = - (await firstValueFrom(this.masterPasswordService.forceSetPasswordReason$(userId))) != - ForceSetPasswordReason.None; await this.systemService.clearPendingClipboard(); @@ -1504,8 +1524,6 @@ export default class MainBackground { this.messagingService.send("goHome"); } else if (nextAccountStatus === AuthenticationStatus.Locked) { this.messagingService.send("locked", { userId: userId }); - } else if (forcePasswordReset) { - this.messagingService.send("update-temp-password", { userId: userId }); } else { this.messagingService.send("unlocked", { userId: userId }); await this.refreshBadge(); diff --git a/apps/browser/src/background/nativeMessaging.background.ts b/apps/browser/src/background/nativeMessaging.background.ts index d92826765db..03876dba673 100644 --- a/apps/browser/src/background/nativeMessaging.background.ts +++ b/apps/browser/src/background/nativeMessaging.background.ts @@ -1,4 +1,4 @@ -import { delay, filter, firstValueFrom, from, map, race, timer } from "rxjs"; +import { firstValueFrom } from "rxjs"; import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; import { AuthService } from "@bitwarden/common/auth/abstractions/auth.service"; @@ -11,7 +11,7 @@ import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/pl 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 { KeyService, BiometricStateService, BiometricsCommands } from "@bitwarden/key-management"; +import { KeyService, BiometricStateService } from "@bitwarden/key-management"; import { BrowserApi } from "../platform/browser/browser-api"; @@ -81,9 +81,6 @@ export class NativeMessagingBackground { private messageId = 0; private callbacks = new Map(); - - isConnectedToOutdatedDesktopClient = true; - constructor( private keyService: KeyService, private encryptService: EncryptService, @@ -137,7 +134,6 @@ export class NativeMessagingBackground { // Safari has a bundled native component which is always available, no need to // check if the desktop app is running. if (this.platformUtilsService.isSafari()) { - this.isConnectedToOutdatedDesktopClient = false; connectedCallback(); } @@ -189,14 +185,6 @@ export class NativeMessagingBackground { this.secureChannel.sharedSecret = new SymmetricCryptoKey(decrypted); this.logService.info("[Native Messaging IPC] Secure channel established"); - if ("messageId" in message) { - this.logService.info("[Native Messaging IPC] Non-legacy desktop client"); - this.isConnectedToOutdatedDesktopClient = false; - } else { - this.logService.info("[Native Messaging IPC] Legacy desktop client"); - this.isConnectedToOutdatedDesktopClient = true; - } - this.secureChannel.setupResolve(); break; } @@ -286,29 +274,6 @@ export class NativeMessagingBackground { async callCommand(message: Message): Promise { const messageId = this.messageId++; - if ( - message.command == BiometricsCommands.Unlock || - message.command == BiometricsCommands.IsAvailable - ) { - // TODO remove after 2025.3 - // wait until there is no other callbacks, or timeout - const call = await firstValueFrom( - race( - from([false]).pipe(delay(5000)), - timer(0, 100).pipe( - filter(() => this.callbacks.size === 0), - map(() => true), - ), - ), - ); - if (!call) { - this.logService.info( - `[Native Messaging IPC] Message of type ${message.command} did not get a response before timing out`, - ); - return; - } - } - const callback = new Promise((resolver, rejecter) => { this.callbacks.set(messageId, { resolver, rejecter }); }); @@ -357,7 +322,7 @@ export class NativeMessagingBackground { await this.secureCommunication(); } - return await this.encryptService.encrypt( + return await this.encryptService.encryptString( JSON.stringify(message), this.secureChannel!.sharedSecret!, ); @@ -401,10 +366,9 @@ export class NativeMessagingBackground { return; } message = JSON.parse( - await this.encryptService.decryptToUtf8( + await this.encryptService.decryptString( rawMessage as EncString, this.secureChannel.sharedSecret, - "ipc-desktop-ipc-channel-key", ), ); } else { @@ -418,22 +382,6 @@ export class NativeMessagingBackground { const messageId = message.messageId; - if ( - message.command == BiometricsCommands.Unlock || - message.command == BiometricsCommands.IsAvailable - ) { - this.logService.info( - `[Native Messaging IPC] Received legacy message of type ${message.command}`, - ); - const messageId: number | undefined = this.callbacks.keys().next().value; - if (messageId != null) { - const resolver = this.callbacks.get(messageId); - this.callbacks.delete(messageId); - resolver!.resolver(message); - } - return; - } - if (this.callbacks.has(messageId)) { const callback = this.callbacks!.get(messageId)!; this.callbacks.delete(messageId); diff --git a/apps/browser/src/billing/popup/settings/premium-v2.component.ts b/apps/browser/src/billing/popup/settings/premium-v2.component.ts index ff0e8efd646..fde44688349 100644 --- a/apps/browser/src/billing/popup/settings/premium-v2.component.ts +++ b/apps/browser/src/billing/popup/settings/premium-v2.component.ts @@ -29,7 +29,6 @@ import { PopupPageComponent } from "../../../platform/popup/layout/popup-page.co @Component({ selector: "app-premium", templateUrl: "premium-v2.component.html", - standalone: true, imports: [ ButtonModule, CardComponent, diff --git a/apps/browser/src/images/app-store.png b/apps/browser/src/images/app-store.png new file mode 100644 index 00000000000..7b3c9759ef9 Binary files /dev/null and b/apps/browser/src/images/app-store.png differ diff --git a/apps/browser/src/images/download-qr.png b/apps/browser/src/images/download-qr.png new file mode 100644 index 00000000000..4362c1616f4 Binary files /dev/null and b/apps/browser/src/images/download-qr.png differ diff --git a/apps/browser/src/images/google-play.png b/apps/browser/src/images/google-play.png new file mode 100644 index 00000000000..3ff87a25d5c Binary files /dev/null and b/apps/browser/src/images/google-play.png differ diff --git a/apps/browser/src/key-management/biometrics/background-browser-biometrics.service.ts b/apps/browser/src/key-management/biometrics/background-browser-biometrics.service.ts index a8a89d45274..ef01ade7390 100644 --- a/apps/browser/src/key-management/biometrics/background-browser-biometrics.service.ts +++ b/apps/browser/src/key-management/biometrics/background-browser-biometrics.service.ts @@ -35,17 +35,10 @@ export class BackgroundBrowserBiometricsService extends BiometricsService { try { await this.ensureConnected(); - if (this.nativeMessagingBackground().isConnectedToOutdatedDesktopClient) { - const response = await this.nativeMessagingBackground().callCommand({ - command: BiometricsCommands.Unlock, - }); - return response.response == "unlocked"; - } else { - const response = await this.nativeMessagingBackground().callCommand({ - command: BiometricsCommands.AuthenticateWithBiometrics, - }); - return response.response; - } + const response = await this.nativeMessagingBackground().callCommand({ + command: BiometricsCommands.AuthenticateWithBiometrics, + }); + return response.response; } catch (e) { this.logService.info("Biometric authentication failed", e); return false; @@ -60,23 +53,12 @@ export class BackgroundBrowserBiometricsService extends BiometricsService { try { await this.ensureConnected(); - if (this.nativeMessagingBackground().isConnectedToOutdatedDesktopClient) { - const response = await this.nativeMessagingBackground().callCommand({ - command: BiometricsCommands.IsAvailable, - }); - const resp = - response.response == "available" - ? BiometricsStatus.Available - : BiometricsStatus.HardwareUnavailable; - return resp; - } else { - const response = await this.nativeMessagingBackground().callCommand({ - command: BiometricsCommands.GetBiometricsStatus, - }); + const response = await this.nativeMessagingBackground().callCommand({ + command: BiometricsCommands.GetBiometricsStatus, + }); - if (response.response) { - return response.response; - } + if (response.response) { + return response.response; } return BiometricsStatus.Available; // FIXME: Remove when updating file. Eslint update @@ -90,43 +72,23 @@ export class BackgroundBrowserBiometricsService extends BiometricsService { try { await this.ensureConnected(); - // todo remove after 2025.3 - if (this.nativeMessagingBackground().isConnectedToOutdatedDesktopClient) { - const response = await this.nativeMessagingBackground().callCommand({ - command: BiometricsCommands.Unlock, - }); - if (response.response == "unlocked") { - const decodedUserkey = Utils.fromB64ToArray(response.userKeyB64); - const userKey = new SymmetricCryptoKey(decodedUserkey) as UserKey; - if (await this.keyService.validateUserKey(userKey, userId)) { - await this.biometricStateService.setBiometricUnlockEnabled(true); - await this.keyService.setUserKey(userKey, userId); - // to update badge and other things - this.messagingService.send("switchAccount", { userId }); - return userKey; - } - } else { - return null; + const response = await this.nativeMessagingBackground().callCommand({ + command: BiometricsCommands.UnlockWithBiometricsForUser, + userId: userId, + }); + if (response.response) { + // In case the requesting foreground context dies (popup), the userkey should still be set, so the user is unlocked / the setting should be enabled + const decodedUserkey = Utils.fromB64ToArray(response.userKeyB64); + const userKey = new SymmetricCryptoKey(decodedUserkey) as UserKey; + if (await this.keyService.validateUserKey(userKey, userId)) { + await this.biometricStateService.setBiometricUnlockEnabled(true); + await this.keyService.setUserKey(userKey, userId); + // to update badge and other things + this.messagingService.send("switchAccount", { userId }); + return userKey; } } else { - const response = await this.nativeMessagingBackground().callCommand({ - command: BiometricsCommands.UnlockWithBiometricsForUser, - userId: userId, - }); - if (response.response) { - // In case the requesting foreground context dies (popup), the userkey should still be set, so the user is unlocked / the setting should be enabled - const decodedUserkey = Utils.fromB64ToArray(response.userKeyB64); - const userKey = new SymmetricCryptoKey(decodedUserkey) as UserKey; - if (await this.keyService.validateUserKey(userKey, userId)) { - await this.biometricStateService.setBiometricUnlockEnabled(true); - await this.keyService.setUserKey(userKey, userId); - // to update badge and other things - this.messagingService.send("switchAccount", { userId }); - return userKey; - } - } else { - return null; - } + return null; } } catch (e) { this.logService.info("Biometric unlock for user failed", e); @@ -140,10 +102,6 @@ export class BackgroundBrowserBiometricsService extends BiometricsService { try { await this.ensureConnected(); - if (this.nativeMessagingBackground().isConnectedToOutdatedDesktopClient) { - return await this.getBiometricsStatus(); - } - return ( await this.nativeMessagingBackground().callCommand({ command: BiometricsCommands.GetBiometricsStatusForUser, @@ -161,7 +119,7 @@ export class BackgroundBrowserBiometricsService extends BiometricsService { private async ensureConnected() { if (!this.nativeMessagingBackground().connected) { await this.nativeMessagingBackground().callCommand({ - command: BiometricsCommands.IsAvailable, + command: BiometricsCommands.GetBiometricsStatus, }); } } diff --git a/apps/browser/src/auth/popup/remove-password.component.html b/apps/browser/src/key-management/key-connector/remove-password.component.html similarity index 57% rename from apps/browser/src/auth/popup/remove-password.component.html rename to apps/browser/src/key-management/key-connector/remove-password.component.html index 793bcff3e09..56baf0de2a0 100644 --- a/apps/browser/src/auth/popup/remove-password.component.html +++ b/apps/browser/src/key-management/key-connector/remove-password.component.html @@ -8,17 +8,21 @@
-
+
+
+ +
+
+
-

{{ "convertOrganizationEncryptionDesc" | i18n: organization.name }}

+

{{ "removeMasterPasswordForOrganizationUserKeyConnector" | i18n }}

+

{{ "organizationName" | i18n }}:

+

{{ organization.name }}

+

{{ "keyConnectorDomain" | i18n }}:

+

{{ organization.keyConnectorUrl }}

-
`, - standalone: true, }) class ExtensionContainerComponent {} @@ -70,7 +70,6 @@ class ExtensionContainerComponent {} `, - standalone: true, imports: [CommonModule, ItemModule, BadgeModule, IconButtonModule, SectionComponent], }) class VaultComponent { @@ -80,12 +79,11 @@ class VaultComponent { @Component({ selector: "mock-add-button", template: ` - `, - standalone: true, imports: [ButtonModule], }) class MockAddButtonComponent {} @@ -101,7 +99,6 @@ class MockAddButtonComponent {} aria-label="Pop out" > `, - standalone: true, imports: [IconButtonModule], }) class MockPopoutButtonComponent {} @@ -113,7 +110,6 @@ class MockPopoutButtonComponent {} `, - standalone: true, imports: [AvatarModule], }) class MockCurrentAccountComponent {} @@ -121,7 +117,6 @@ class MockCurrentAccountComponent {} @Component({ selector: "mock-search", template: ` `, - standalone: true, imports: [SearchModule], }) class MockSearchComponent {} @@ -133,7 +128,6 @@ class MockSearchComponent {} This is an important note about these ciphers `, - standalone: true, imports: [BannerModule], }) class MockBannerComponent {} @@ -153,7 +147,6 @@ class MockBannerComponent {} `, - standalone: true, imports: [ PopupPageComponent, PopupHeaderComponent, @@ -179,12 +172,10 @@ class MockVaultPageComponent {} `, - standalone: true, imports: [ PopupPageComponent, PopupHeaderComponent, MockAddButtonComponent, - MockPopoutButtonComponent, MockCurrentAccountComponent, VaultComponent, ], @@ -205,7 +196,6 @@ class MockVaultPagePoppedComponent {}
Generator content here
`, - standalone: true, imports: [ PopupPageComponent, PopupHeaderComponent, @@ -230,7 +220,6 @@ class MockGeneratorPageComponent {}
Send content here
`, - standalone: true, imports: [ PopupPageComponent, PopupHeaderComponent, @@ -255,7 +244,6 @@ class MockSendPageComponent {}
Settings content here
`, - standalone: true, imports: [ PopupPageComponent, PopupHeaderComponent, @@ -283,15 +271,12 @@ class MockSettingsPageComponent {} `, - standalone: true, imports: [ PopupPageComponent, PopupHeaderComponent, PopupFooterComponent, ButtonModule, - MockAddButtonComponent, MockPopoutButtonComponent, - MockCurrentAccountComponent, VaultComponent, IconButtonModule, ], @@ -340,6 +325,7 @@ export default { generator: "Generator", send: "Send", settings: "Settings", + labelWithNotification: (label: string) => `${label}: New Notification`, }); }, }, @@ -398,17 +384,64 @@ export default { type Story = StoryObj; -export const PopupTabNavigation: Story = { +type PopupTabNavigationStory = StoryObj; + +const navButtons = (showBerry = false) => [ + { + label: "vault", + page: "/tabs/vault", + icon: Icons.VaultInactive, + iconActive: Icons.VaultActive, + }, + { + label: "generator", + page: "/tabs/generator", + icon: Icons.GeneratorInactive, + iconActive: Icons.GeneratorActive, + }, + { + label: "send", + page: "/tabs/send", + icon: Icons.SendInactive, + iconActive: Icons.SendActive, + }, + { + label: "settings", + page: "/tabs/settings", + icon: Icons.SettingsInactive, + iconActive: Icons.SettingsActive, + 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-page.component.ts b/apps/browser/src/platform/popup/layout/popup-page.component.ts index ca019c16bd7..12bd000ca55 100644 --- a/apps/browser/src/platform/popup/layout/popup-page.component.ts +++ b/apps/browser/src/platform/popup/layout/popup-page.component.ts @@ -6,7 +6,6 @@ import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.servic @Component({ selector: "popup-page", templateUrl: "popup-page.component.html", - standalone: true, host: { class: "tw-h-full tw-flex tw-flex-col tw-overflow-y-hidden", }, diff --git a/apps/browser/src/platform/popup/layout/popup-size.service.ts b/apps/browser/src/platform/popup/layout/popup-size.service.ts index 3ae9a633cab..69d3102d24e 100644 --- a/apps/browser/src/platform/popup/layout/popup-size.service.ts +++ b/apps/browser/src/platform/popup/layout/popup-size.service.ts @@ -50,17 +50,40 @@ export class PopupSizeService { PopupSizeService.setStyle(width); localStorage.setItem(PopupSizeService.LocalStorageKey, width); }); + } + async setHeight() { const isInChromeTab = await BrowserPopupUtils.isInTab(); + /** + * To support both browser default zoom and system default zoom, we need to take into account + * the full screen height. When system default zoom is >100%, window.innerHeight still outputs + * a height equivalent to what it would be at 100%, which can cause the extension window to + * render as too tall. So if the screen height is smaller than the max possible extension height, + * we should use that to set our extension height. Otherwise, we want to use the window.innerHeight + * to support browser zoom. + * + * This is basically a workaround for what we consider a bug with browsers reporting the wrong + * available innerHeight when system zoom is turned on. If that gets fixed, we can remove the code + * checking the screen height. + */ + const MAX_EXT_HEIGHT = 600; + const extensionInnerHeight = window.innerHeight; + // Use a 100px offset when calculating screen height to account for browser container elements + const screenAvailHeight = window.screen.availHeight - 100; + const availHeight = + screenAvailHeight < MAX_EXT_HEIGHT ? screenAvailHeight : extensionInnerHeight; + if (!BrowserPopupUtils.inPopup(window) || isInChromeTab) { - window.document.body.classList.add("body-full"); - } else if (window.innerHeight < 400) { - window.document.body.classList.add("body-xxs"); - } else if (window.innerHeight < 500) { - window.document.body.classList.add("body-xs"); - } else if (window.innerHeight < 600) { - window.document.body.classList.add("body-sm"); + window.document.documentElement.classList.add("body-full"); + } else if (availHeight < 300) { + window.document.documentElement.classList.add("body-3xs"); + } else if (availHeight < 400) { + window.document.documentElement.classList.add("body-xxs"); + } else if (availHeight < 500) { + window.document.documentElement.classList.add("body-xs"); + } else if (availHeight < 600) { + window.document.documentElement.classList.add("body-sm"); } } 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..1170725a4b7 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,32 +5,31 @@
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..8a897e2e21b 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,44 +1,34 @@ 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 { LinkModule } from "@bitwarden/components"; +import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; +import { Icon, IconModule, LinkModule } from "@bitwarden/components"; + +export type NavButton = { + label: string; + page: string; + icon: Icon; + iconActive: Icon; + showBerry?: boolean; +}; @Component({ selector: "popup-tab-navigation", templateUrl: "popup-tab-navigation.component.html", - standalone: true, - imports: [CommonModule, LinkModule, RouterModule, JslibModule], + imports: [CommonModule, LinkModule, RouterModule, JslibModule, IconModule], host: { class: "tw-block tw-h-full tw-w-full tw-flex tw-flex-col", }, }) 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/services/browser-router.service.ts b/apps/browser/src/platform/popup/services/browser-router.service.ts index 413bde5fcad..2d449b8a0f2 100644 --- a/apps/browser/src/platform/popup/services/browser-router.service.ts +++ b/apps/browser/src/platform/popup/services/browser-router.service.ts @@ -21,6 +21,8 @@ export class BrowserRouterService { child = child.firstChild; } + // TODO: Eslint upgrade. Please resolve this since the ?? does nothing + // eslint-disable-next-line no-constant-binary-expression const updateUrl = !child?.data?.doNotSaveUrl ?? true; if (updateUrl) { diff --git a/apps/browser/src/platform/popup/view-cache/popup-router-cache.service.ts b/apps/browser/src/platform/popup/view-cache/popup-router-cache.service.ts index 3215893c634..5fc508ac2a6 100644 --- a/apps/browser/src/platform/popup/view-cache/popup-router-cache.service.ts +++ b/apps/browser/src/platform/popup/view-cache/popup-router-cache.service.ts @@ -8,8 +8,9 @@ import { NavigationEnd, Router, UrlSerializer, + UrlTree, } from "@angular/router"; -import { filter, first, firstValueFrom, map, Observable, switchMap, tap } from "rxjs"; +import { filter, first, firstValueFrom, map, Observable, of, switchMap, tap } from "rxjs"; import { GlobalStateProvider } from "@bitwarden/common/platform/state"; @@ -31,6 +32,11 @@ export class PopupRouterCacheService { private hasNavigated = false; + private _hasRestoredCache = false; + get hasRestoredCache() { + return this._hasRestoredCache; + } + constructor() { // init history with existing state this.history$() @@ -56,6 +62,8 @@ export class PopupRouterCacheService { child = child.firstChild; } + // TODO: Eslint upgrade. Please resolve this since the ?? does nothing + // eslint-disable-next-line no-constant-binary-expression return !child?.data?.doNotSaveUrl ?? true; }), switchMap((event) => this.push(event.url)), @@ -107,21 +115,34 @@ export class PopupRouterCacheService { // if no history is present, fallback to vault page await this.router.navigate([""]); } + + /** + * Mark the cache as restored to prevent the router `popupRouterCacheGuard` from + * redirecting to the last visited route again this session. + */ + markCacheRestored() { + this._hasRestoredCache = true; + } } /** * Redirect to the last visited route. Should be applied to root route. **/ -export const popupRouterCacheGuard = (() => { +export const popupRouterCacheGuard = ((): Observable => { const popupHistoryService = inject(PopupRouterCacheService); const urlSerializer = inject(UrlSerializer); + if (popupHistoryService.hasRestoredCache) { + return of(true); + } + return popupHistoryService.last$().pipe( map((url: string) => { if (!url) { return true; } + popupHistoryService.markCacheRestored(); return urlSerializer.parse(url); }), ); diff --git a/apps/browser/src/platform/popup/view-cache/popup-router-cache.spec.ts b/apps/browser/src/platform/popup/view-cache/popup-router-cache.spec.ts index 465a6e6c69c..22fb7bf99b9 100644 --- a/apps/browser/src/platform/popup/view-cache/popup-router-cache.spec.ts +++ b/apps/browser/src/platform/popup/view-cache/popup-router-cache.spec.ts @@ -13,7 +13,10 @@ import { PopupRouterCacheService, popupRouterCacheGuard } from "./popup-router-c const flushPromises = async () => await new Promise(process.nextTick); -@Component({ template: "" }) +@Component({ + template: "", + standalone: false, +}) export class EmptyComponent {} describe("Popup router cache guard", () => { 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..83d6edbc141 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 @@ -19,7 +19,7 @@ import { FormCacheOptions, SignalCacheOptions, ViewCacheService, -} from "@bitwarden/angular/platform/abstractions/view-cache.service"; +} from "@bitwarden/angular/platform/view-cache"; import { MessageSender } from "@bitwarden/common/platform/messaging"; import { GlobalStateProvider } from "@bitwarden/common/platform/state"; @@ -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,22 @@ export class PopupViewCacheService implements ViewCacheService { key, injector = inject(Injector), initialValue, + persistNavigation, + clearOnTabChange, } = options; - const cachedValue = this.cache[key] ? deserializer(JSON.parse(this.cache[key])) : initialValue; + const cachedValue = this.cache[key]?.value + ? deserializer(JSON.parse(this.cache[key].value)) + : initialValue; const _signal = signal(cachedValue); + const viewCacheOptions = { + ...(persistNavigation && { persistNavigation }), + ...(clearOnTabChange && { clearOnTabChange }), + }; + effect( () => { - this.updateState(key, JSON.stringify(_signal())); + this.updateState(key, JSON.stringify(_signal()), viewCacheOptions); }, { injector }, ); @@ -123,15 +127,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..60baf94eeae 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,14 +14,21 @@ 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"; -@Component({ template: "" }) +@Component({ + template: "", + standalone: false, +}) export class EmptyComponent {} -@Component({ template: "" }) +@Component({ + template: "", + standalone: false, +}) export class TestComponent { private viewCacheService = inject(PopupViewCacheService); @@ -35,6 +42,7 @@ export class TestComponent { signal = this.viewCacheService.signal({ key: "test-signal", initialValue: "initial signal", + persistNavigation: true, }); } @@ -42,11 +50,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 +114,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 +132,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 +154,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 +198,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 +215,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/local-backed-session-storage.service.spec.ts b/apps/browser/src/platform/services/local-backed-session-storage.service.spec.ts index 1b93e33a94e..1b4665b3222 100644 --- a/apps/browser/src/platform/services/local-backed-session-storage.service.spec.ts +++ b/apps/browser/src/platform/services/local-backed-session-storage.service.spec.ts @@ -46,22 +46,18 @@ describe("LocalBackedSessionStorage", () => { it("returns a decrypted value when one is stored in local storage", async () => { const encrypted = makeEncString("encrypted"); localStorage.internalStore["session_test"] = encrypted.encryptedString; - encryptService.decryptToUtf8.mockResolvedValue(JSON.stringify("decrypted")); + encryptService.decryptString.mockResolvedValue(JSON.stringify("decrypted")); const result = await sut.get("test"); // FIXME: Remove when updating file. Eslint update // eslint-disable-next-line @typescript-eslint/no-unused-expressions - expect(encryptService.decryptToUtf8).toHaveBeenCalledWith( - encrypted, - sessionKey, - "browser-session-key", - ), + expect(encryptService.decryptString).toHaveBeenCalledWith(encrypted, sessionKey), expect(result).toEqual("decrypted"); }); it("caches the decrypted value when one is stored in local storage", async () => { const encrypted = makeEncString("encrypted"); localStorage.internalStore["session_test"] = encrypted.encryptedString; - encryptService.decryptToUtf8.mockResolvedValue(JSON.stringify("decrypted")); + encryptService.decryptString.mockResolvedValue(JSON.stringify("decrypted")); await sut.get("test"); expect(sut["cache"]["test"]).toEqual("decrypted"); }); @@ -69,22 +65,18 @@ describe("LocalBackedSessionStorage", () => { it("returns a decrypted value when one is stored in local storage", async () => { const encrypted = makeEncString("encrypted"); localStorage.internalStore["session_test"] = encrypted.encryptedString; - encryptService.decryptToUtf8.mockResolvedValue(JSON.stringify("decrypted")); + encryptService.decryptString.mockResolvedValue(JSON.stringify("decrypted")); const result = await sut.get("test"); // FIXME: Remove when updating file. Eslint update // eslint-disable-next-line @typescript-eslint/no-unused-expressions - expect(encryptService.decryptToUtf8).toHaveBeenCalledWith( - encrypted, - sessionKey, - "browser-session-key", - ), + expect(encryptService.decryptString).toHaveBeenCalledWith(encrypted, sessionKey), expect(result).toEqual("decrypted"); }); it("caches the decrypted value when one is stored in local storage", async () => { const encrypted = makeEncString("encrypted"); localStorage.internalStore["session_test"] = encrypted.encryptedString; - encryptService.decryptToUtf8.mockResolvedValue(JSON.stringify("decrypted")); + encryptService.decryptString.mockResolvedValue(JSON.stringify("decrypted")); await sut.get("test"); expect(sut["cache"]["test"]).toEqual("decrypted"); }); @@ -104,7 +96,7 @@ describe("LocalBackedSessionStorage", () => { it("returns true when the key is in local storage", async () => { localStorage.internalStore["session_test"] = makeEncString("encrypted").encryptedString; - encryptService.decryptToUtf8.mockResolvedValue(JSON.stringify("decrypted")); + encryptService.decryptString.mockResolvedValue(JSON.stringify("decrypted")); const result = await sut.has("test"); expect(result).toBe(true); }); @@ -119,7 +111,7 @@ describe("LocalBackedSessionStorage", () => { async (nullish) => { localStorage.internalStore["session_test"] = nullish; await expect(sut.has("test")).resolves.toBe(false); - expect(encryptService.decryptToUtf8).not.toHaveBeenCalled(); + expect(encryptService.decryptString).not.toHaveBeenCalled(); }, ); }); @@ -127,7 +119,7 @@ describe("LocalBackedSessionStorage", () => { describe("save", () => { const encString = makeEncString("encrypted"); beforeEach(() => { - encryptService.encrypt.mockResolvedValue(encString); + encryptService.encryptString.mockResolvedValue(encString); }); it("logs a warning when saving the same value twice and in a dev environment", async () => { @@ -157,7 +149,10 @@ describe("LocalBackedSessionStorage", () => { it("encrypts and saves the value to local storage", async () => { await sut.save("test", "value"); - expect(encryptService.encrypt).toHaveBeenCalledWith(JSON.stringify("value"), sessionKey); + expect(encryptService.encryptString).toHaveBeenCalledWith( + JSON.stringify("value"), + sessionKey, + ); expect(localStorage.internalStore["session_test"]).toEqual(encString.encryptedString); }); diff --git a/apps/browser/src/platform/services/local-backed-session-storage.service.ts b/apps/browser/src/platform/services/local-backed-session-storage.service.ts index 0e6922e3083..1507bf20c48 100644 --- a/apps/browser/src/platform/services/local-backed-session-storage.service.ts +++ b/apps/browser/src/platform/services/local-backed-session-storage.service.ts @@ -118,11 +118,7 @@ export class LocalBackedSessionStorageService return null; } - const valueJson = await this.encryptService.decryptToUtf8( - new EncString(local), - encKey, - "browser-session-key", - ); + const valueJson = await this.encryptService.decryptString(new EncString(local), encKey); if (valueJson == null) { // error with decryption, value is lost, delete state and start over await this.localStorage.remove(this.sessionStorageKey(key)); @@ -139,7 +135,10 @@ export class LocalBackedSessionStorageService } const valueJson = JSON.stringify(value); - const encValue = await this.encryptService.encrypt(valueJson, await this.sessionKey.get()); + const encValue = await this.encryptService.encryptString( + valueJson, + await this.sessionKey.get(), + ); await this.localStorage.save(this.sessionStorageKey(key), encValue.encryptedString); } diff --git a/apps/browser/src/platform/services/platform-utils/browser-platform-utils.service.spec.ts b/apps/browser/src/platform/services/platform-utils/browser-platform-utils.service.spec.ts index 38166d10a08..f75e9cc29a5 100644 --- a/apps/browser/src/platform/services/platform-utils/browser-platform-utils.service.spec.ts +++ b/apps/browser/src/platform/services/platform-utils/browser-platform-utils.service.spec.ts @@ -126,12 +126,11 @@ describe("Browser Utils Service", () => { configurable: true, value: "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:58.0) Gecko/20100101 Firefox/58.0", }); - jest.spyOn(BrowserPlatformUtilsService, "isFirefox"); browserPlatformUtilsService.getDevice(); expect(browserPlatformUtilsService.getDevice()).toBe(DeviceType.FirefoxExtension); - expect(BrowserPlatformUtilsService.isFirefox).toHaveBeenCalledTimes(1); + expect(browserPlatformUtilsService.isFirefox()).toBe(true); }); }); diff --git a/apps/browser/src/platform/services/platform-utils/browser-platform-utils.service.ts b/apps/browser/src/platform/services/platform-utils/browser-platform-utils.service.ts index c9200ecc1a4..4ae412fbda6 100644 --- a/apps/browser/src/platform/services/platform-utils/browser-platform-utils.service.ts +++ b/apps/browser/src/platform/services/platform-utils/browser-platform-utils.service.ts @@ -26,6 +26,10 @@ export abstract class BrowserPlatformUtilsService implements PlatformUtilsServic return this.deviceCache; } + // ORDERING MATTERS HERE + // Ordered from most specific to least specific. We try to discern the greatest detail + // for the type of extension the user is on by checking specific cases first and as we go down + // the list we hope to catch all by the most generic clients they could be on. if (BrowserPlatformUtilsService.isFirefox()) { this.deviceCache = DeviceType.FirefoxExtension; } else if (BrowserPlatformUtilsService.isOpera(globalContext)) { @@ -56,10 +60,7 @@ export abstract class BrowserPlatformUtilsService implements PlatformUtilsServic return ClientType.Browser; } - /** - * @deprecated Do not call this directly, use getDevice() instead - */ - static isFirefox(): boolean { + private static isFirefox(): boolean { return ( navigator.userAgent.indexOf(" Firefox/") !== -1 || navigator.userAgent.indexOf(" Gecko/") !== -1 @@ -70,9 +71,6 @@ export abstract class BrowserPlatformUtilsService implements PlatformUtilsServic return this.getDevice() === DeviceType.FirefoxExtension; } - /** - * @deprecated Do not call this directly, use getDevice() instead - */ private static isChrome(globalContext: Window | ServiceWorkerGlobalScope): boolean { return globalContext.chrome && navigator.userAgent.indexOf(" Chrome/") !== -1; } @@ -81,9 +79,6 @@ export abstract class BrowserPlatformUtilsService implements PlatformUtilsServic return this.getDevice() === DeviceType.ChromeExtension; } - /** - * @deprecated Do not call this directly, use getDevice() instead - */ private static isEdge(): boolean { return navigator.userAgent.indexOf(" Edg/") !== -1; } @@ -92,9 +87,6 @@ export abstract class BrowserPlatformUtilsService implements PlatformUtilsServic return this.getDevice() === DeviceType.EdgeExtension; } - /** - * @deprecated Do not call this directly, use getDevice() instead - */ private static isOpera(globalContext: Window | ServiceWorkerGlobalScope): boolean { return ( !!globalContext.opr?.addons || @@ -107,9 +99,6 @@ export abstract class BrowserPlatformUtilsService implements PlatformUtilsServic return this.getDevice() === DeviceType.OperaExtension; } - /** - * @deprecated Do not call this directly, use getDevice() instead - */ private static isVivaldi(): boolean { return navigator.userAgent.indexOf(" Vivaldi/") !== -1; } @@ -118,10 +107,7 @@ export abstract class BrowserPlatformUtilsService implements PlatformUtilsServic return this.getDevice() === DeviceType.VivaldiExtension; } - /** - * @deprecated Do not call this directly, use getDevice() instead - */ - static isSafari(globalContext: Window | ServiceWorkerGlobalScope): boolean { + private static isSafari(globalContext: Window | ServiceWorkerGlobalScope): boolean { // Opera masquerades as Safari, so make sure we're not there first return ( !BrowserPlatformUtilsService.isOpera(globalContext) && @@ -133,6 +119,10 @@ export abstract class BrowserPlatformUtilsService implements PlatformUtilsServic return navigator.userAgent.match("Version/([0-9.]*)")?.[1]; } + isSafari(): boolean { + return this.getDevice() === DeviceType.SafariExtension; + } + /** * Safari previous to version 16.1 had a bug which caused artifacts on hover in large extension popups. * https://bugs.webkit.org/show_bug.cgi?id=218704 @@ -147,10 +137,6 @@ export abstract class BrowserPlatformUtilsService implements PlatformUtilsServic return parts?.[0] < 16 || (parts?.[0] === 16 && parts?.[1] === 0); } - isSafari(): boolean { - return this.getDevice() === DeviceType.SafariExtension; - } - isIE(): boolean { return false; } 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..49eae15fbbd 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 @@ -1,4 +1,4 @@ -import { switchMap, delay, filter, concatMap } from "rxjs"; +import { switchMap, delay, filter, concatMap, map, first, of } from "rxjs"; import { CommandDefinition, MessageListener } from "@bitwarden/common/platform/messaging"; import { @@ -12,12 +12,38 @@ import { GlobalStateProvider, } from "@bitwarden/common/platform/state"; +import { BrowserApi } from "../browser/browser-api"; 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; + + /** + * When set, the cached value will be cleared when the user changes tabs. + * @optional + */ + clearOnTabChange?: true; +}; + +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 +62,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 +93,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 +107,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 @@ -89,6 +136,37 @@ export class PopupViewCacheBackgroundService { ), ) .subscribe(); + + // On tab changed, excluding extension tabs + fromChromeEvent(chrome.tabs.onActivated) + .pipe( + switchMap((tabs) => BrowserApi.getTab(tabs[0].tabId)!), + switchMap((tab) => { + // FireFox sets the `url` to "about:blank" and won't populate the `url` until the `onUpdated` event + if (tab.url !== "about:blank") { + return of(tab); + } + + return fromChromeEvent(chrome.tabs.onUpdated).pipe( + first(), + switchMap(([tabId]) => BrowserApi.getTab(tabId)!), + ); + }), + map((tab) => tab.url || tab.pendingUrl), + filter((url) => !url?.startsWith(chrome.runtime.getURL(""))), + switchMap(() => + this.popupViewCacheState.update((state) => { + if (!state) { + return null; + } + // Only remove keys that are marked with `clearOnTabChange` + return Object.fromEntries( + Object.entries(state).filter(([, { options }]) => !options?.clearOnTabChange), + ); + }), + ), + ) + .subscribe(); } async clearState() { 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..2ac75bbec2c 100644 --- a/apps/browser/src/platform/sync/foreground-sync.service.ts +++ b/apps/browser/src/platform/sync/foreground-sync.service.ts @@ -13,7 +13,10 @@ import { } from "@bitwarden/common/platform/messaging"; import { Utils } from "@bitwarden/common/platform/misc/utils"; import { StateProvider } from "@bitwarden/common/platform/state"; +// This import has been flagged as unallowed for this class. It may be involved in a circular dependency loop. +// eslint-disable-next-line no-restricted-imports import { 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 +25,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 +63,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 +93,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..dc0674a7ae5 100644 --- a/apps/browser/src/platform/sync/sync-service.listener.spec.ts +++ b/apps/browser/src/platform/sync/sync-service.listener.spec.ts @@ -3,6 +3,8 @@ import { Subject, firstValueFrom } from "rxjs"; import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; import { MessageListener, MessageSender } from "@bitwarden/common/platform/messaging"; +// This import has been flagged as unallowed for this class. It may be involved in a circular dependency loop. +// eslint-disable-next-line no-restricted-imports import { tagAsExternal } from "@bitwarden/common/platform/messaging/helpers"; import { SyncService } from "@bitwarden/common/vault/abstractions/sync/sync.service.abstraction"; @@ -27,11 +29,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 +54,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/platform/utils/from-chrome-runtime-messaging.ts b/apps/browser/src/platform/utils/from-chrome-runtime-messaging.ts index ebc01ad86fa..9489c5f2a4e 100644 --- a/apps/browser/src/platform/utils/from-chrome-runtime-messaging.ts +++ b/apps/browser/src/platform/utils/from-chrome-runtime-messaging.ts @@ -1,6 +1,8 @@ import { map, share } from "rxjs"; import { Message } from "@bitwarden/common/platform/messaging"; +// This import has been flagged as unallowed for this class. It may be involved in a circular dependency loop. +// eslint-disable-next-line no-restricted-imports import { tagAsExternal } from "@bitwarden/common/platform/messaging/internal"; import { fromChromeEvent } from "../browser/from-chrome-event"; diff --git a/apps/browser/src/popup/app-routing.module.ts b/apps/browser/src/popup/app-routing.module.ts index 21ac4c19700..3dde9f15fdb 100644 --- a/apps/browser/src/popup/app-routing.module.ts +++ b/apps/browser/src/popup/app-routing.module.ts @@ -15,7 +15,6 @@ import { tdeDecryptionRequiredGuard, unauthGuardFn, } from "@bitwarden/angular/auth/guards"; -import { NewDeviceVerificationNoticeGuard } from "@bitwarden/angular/vault/guards"; import { AnonLayoutWrapperComponent, AnonLayoutWrapperData, @@ -43,11 +42,6 @@ import { VaultIcon, } from "@bitwarden/auth/angular"; import { LockComponent } from "@bitwarden/key-management-ui"; -import { - NewDeviceVerificationNoticePageOneComponent, - NewDeviceVerificationNoticePageTwoComponent, - VaultIcons, -} from "@bitwarden/vault"; import { fido2AuthGuard } from "../auth/guards/fido2-auth.guard"; import { AccountSwitcherComponent } from "../auth/popup/account-switching/account-switcher.component"; @@ -55,7 +49,6 @@ import { ExtensionAnonLayoutWrapperComponent, ExtensionAnonLayoutWrapperData, } from "../auth/popup/extension-anon-layout-wrapper/extension-anon-layout-wrapper.component"; -import { RemovePasswordComponent } from "../auth/popup/remove-password.component"; import { SetPasswordComponent } from "../auth/popup/set-password.component"; import { AccountSecurityComponent } from "../auth/popup/settings/account-security.component"; import { UpdateTempPasswordComponent } from "../auth/popup/update-temp-password.component"; @@ -65,6 +58,7 @@ import { BlockedDomainsComponent } from "../autofill/popup/settings/blocked-doma import { ExcludedDomainsComponent } from "../autofill/popup/settings/excluded-domains.component"; import { NotificationsSettingsComponent } from "../autofill/popup/settings/notifications.component"; import { PremiumV2Component } from "../billing/popup/settings/premium-v2.component"; +import { RemovePasswordComponent } from "../key-management/key-connector/remove-password.component"; import BrowserPopupUtils from "../platform/popup/browser-popup-utils"; import { popupRouterCacheGuard } from "../platform/popup/view-cache/popup-router-cache.service"; import { CredentialGeneratorHistoryComponent } from "../tools/popup/generator/credential-generator-history.component"; @@ -73,7 +67,6 @@ import { SendAddEditComponent as SendAddEditV2Component } from "../tools/popup/s import { SendCreatedComponent } from "../tools/popup/send-v2/send-created/send-created.component"; import { SendV2Component } from "../tools/popup/send-v2/send-v2.component"; import { AboutPageV2Component } from "../tools/popup/settings/about-page/about-page-v2.component"; -import { MoreFromBitwardenPageV2Component } from "../tools/popup/settings/about-page/more-from-bitwarden-page-v2.component"; import { ExportBrowserV2Component } from "../tools/popup/settings/export/export-browser-v2.component"; import { ImportBrowserV2Component } from "../tools/popup/settings/import/import-browser-v2.component"; import { SettingsV2Component } from "../tools/popup/settings/settings-v2.component"; @@ -89,7 +82,9 @@ import { PasswordHistoryV2Component } from "../vault/popup/components/vault-v2/v import { VaultV2Component } from "../vault/popup/components/vault-v2/vault-v2.component"; import { ViewV2Component } from "../vault/popup/components/vault-v2/view-v2/view-v2.component"; import { AppearanceV2Component } from "../vault/popup/settings/appearance-v2.component"; +import { DownloadBitwardenComponent } from "../vault/popup/settings/download-bitwarden.component"; import { FoldersV2Component } from "../vault/popup/settings/folders-v2.component"; +import { MoreFromBitwardenPageV2Component } from "../vault/popup/settings/more-from-bitwarden-page-v2.component"; import { TrashComponent } from "../vault/popup/settings/trash.component"; import { VaultSettingsV2Component } from "../vault/popup/settings/vault-settings-v2.component"; @@ -589,6 +584,12 @@ const routes: Routes = [ canActivate: [authGuard], data: { elevation: 2 } satisfies RouteDataProperties, }, + { + path: "download-bitwarden", + component: DownloadBitwardenComponent, + canActivate: [authGuard], + data: { elevation: 2 } satisfies RouteDataProperties, + }, { path: "intro-carousel", component: ExtensionAnonLayoutWrapperComponent, @@ -605,34 +606,6 @@ const routes: Routes = [ }, ], }, - { - path: "new-device-notice", - component: ExtensionAnonLayoutWrapperComponent, - canActivate: [], - children: [ - { - path: "", - component: NewDeviceVerificationNoticePageOneComponent, - data: { - pageIcon: VaultIcons.ExclamationTriangle, - pageTitle: { - key: "importantNotice", - }, - hideFooter: true, - }, - }, - { - path: "setup", - component: NewDeviceVerificationNoticePageTwoComponent, - data: { - pageIcon: VaultIcons.UserLock, - pageTitle: { - key: "setupTwoStepLogin", - }, - }, - }, - ], - }, { path: "tabs", component: TabsV2Component, @@ -650,7 +623,7 @@ const routes: Routes = [ { path: "vault", component: VaultV2Component, - canActivate: [authGuard, NewDeviceVerificationNoticeGuard], + canActivate: [authGuard], canDeactivate: [clearVaultStateGuard], data: { elevation: 0 } satisfies RouteDataProperties, }, diff --git a/apps/browser/src/popup/app.component.ts b/apps/browser/src/popup/app.component.ts index 6a08bf007bb..f009ad064c4 100644 --- a/apps/browser/src/popup/app.component.ts +++ b/apps/browser/src/popup/app.component.ts @@ -1,11 +1,20 @@ // FIXME: Update this file to be type safe and remove this and next line // @ts-strict-ignore -import { ChangeDetectorRef, Component, NgZone, OnDestroy, OnInit, inject } from "@angular/core"; +import { + ChangeDetectorRef, + Component, + DestroyRef, + NgZone, + OnDestroy, + OnInit, + inject, +} from "@angular/core"; import { takeUntilDestroyed } from "@angular/core/rxjs-interop"; import { NavigationEnd, Router, RouterOutlet } from "@angular/router"; import { Subject, takeUntil, firstValueFrom, concatMap, filter, tap } from "rxjs"; import { DeviceTrustToastService } from "@bitwarden/angular/auth/services/device-trust-toast.service.abstraction"; +import { DocumentLangSetter } from "@bitwarden/angular/platform/i18n"; import { LogoutReason } from "@bitwarden/auth/common"; import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; import { AuthService } from "@bitwarden/common/auth/abstractions/auth.service"; @@ -26,6 +35,7 @@ import { import { BiometricsService, BiometricStateService } from "@bitwarden/key-management"; import { PopupCompactModeService } from "../platform/popup/layout/popup-compact-mode.service"; +import { PopupSizeService } from "../platform/popup/layout/popup-size.service"; import { initPopupClosedListener } from "../platform/services/popup-view-cache-background.service"; import { VaultBrowserStateService } from "../vault/services/vault-browser-state.service"; @@ -42,6 +52,7 @@ import { DesktopSyncVerificationDialogComponent } from "./components/desktop-syn
`, + standalone: false, }) export class AppComponent implements OnInit, OnDestroy { private compactModeService = inject(PopupCompactModeService); @@ -71,14 +82,21 @@ export class AppComponent implements OnInit, OnDestroy { private biometricStateService: BiometricStateService, private biometricsService: BiometricsService, private deviceTrustToastService: DeviceTrustToastService, + private readonly destoryRef: DestroyRef, + private readonly documentLangSetter: DocumentLangSetter, + private popupSizeService: PopupSizeService, ) { this.deviceTrustToastService.setupListeners$.pipe(takeUntilDestroyed()).subscribe(); + + const langSubscription = this.documentLangSetter.start(); + this.destoryRef.onDestroy(() => langSubscription.unsubscribe()); } async ngOnInit() { initPopupClosedListener(); this.compactModeService.init(); + await this.popupSizeService.setHeight(); // Component states must not persist between closing and reopening the popup, otherwise they become dead objects // Clear them aggressively to make sure this doesn't occur @@ -160,10 +178,6 @@ export class AppComponent implements OnInit, OnDestroy { // FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling. // eslint-disable-next-line @typescript-eslint/no-floating-promises this.router.navigate(["/remove-password"]); - } else if (msg.command == "update-temp-password") { - // FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling. - // eslint-disable-next-line @typescript-eslint/no-floating-promises - this.router.navigate(["/update-temp-password"]); } }), takeUntil(this.destroy$), diff --git a/apps/browser/src/popup/app.module.ts b/apps/browser/src/popup/app.module.ts index 0d392afa63b..8bea41da4d6 100644 --- a/apps/browser/src/popup/app.module.ts +++ b/apps/browser/src/popup/app.module.ts @@ -25,13 +25,13 @@ import { import { AccountComponent } from "../auth/popup/account-switching/account.component"; import { CurrentAccountComponent } from "../auth/popup/account-switching/current-account.component"; import { ExtensionAnonLayoutWrapperComponent } from "../auth/popup/extension-anon-layout-wrapper/extension-anon-layout-wrapper.component"; -import { RemovePasswordComponent } from "../auth/popup/remove-password.component"; import { SetPasswordComponent } from "../auth/popup/set-password.component"; import { AccountSecurityComponent } from "../auth/popup/settings/account-security.component"; import { VaultTimeoutInputComponent } from "../auth/popup/settings/vault-timeout-input.component"; import { UpdateTempPasswordComponent } from "../auth/popup/update-temp-password.component"; import { AutofillComponent } from "../autofill/popup/settings/autofill.component"; import { NotificationsSettingsComponent } from "../autofill/popup/settings/notifications.component"; +import { RemovePasswordComponent } from "../key-management/key-connector/remove-password.component"; import { PopOutComponent } from "../platform/popup/components/pop-out.component"; import { HeaderComponent } from "../platform/popup/header.component"; import { PopupFooterComponent } from "../platform/popup/layout/popup-footer.component"; diff --git a/apps/browser/src/popup/components/desktop-sync-verification-dialog.component.ts b/apps/browser/src/popup/components/desktop-sync-verification-dialog.component.ts index 5003bbdc936..2ca24da6c75 100644 --- a/apps/browser/src/popup/components/desktop-sync-verification-dialog.component.ts +++ b/apps/browser/src/popup/components/desktop-sync-verification-dialog.component.ts @@ -17,7 +17,6 @@ export type DesktopSyncVerificationDialogParams = { @Component({ templateUrl: "desktop-sync-verification-dialog.component.html", - standalone: true, imports: [JslibModule, ButtonModule, DialogModule], }) export class DesktopSyncVerificationDialogComponent implements OnDestroy, OnInit { diff --git a/apps/browser/src/popup/components/user-verification.component.ts b/apps/browser/src/popup/components/user-verification.component.ts index 6befc8973b0..f6cb6cdff12 100644 --- a/apps/browser/src/popup/components/user-verification.component.ts +++ b/apps/browser/src/popup/components/user-verification.component.ts @@ -22,5 +22,6 @@ import { UserVerificationComponent as BaseComponent } from "@bitwarden/angular/a transition(":enter", [style({ opacity: 0 }), animate("100ms", style({ opacity: 1 }))]), ]), ], + standalone: false, }) export class UserVerificationComponent extends BaseComponent {} diff --git a/apps/browser/src/popup/images/loading.svg b/apps/browser/src/popup/images/loading.svg index 3f2033303db..5f4102a5921 100644 --- a/apps/browser/src/popup/images/loading.svg +++ b/apps/browser/src/popup/images/loading.svg @@ -1,5 +1,5 @@  - Loading... diff --git a/apps/browser/src/popup/scss/base.scss b/apps/browser/src/popup/scss/base.scss index 59893b5050d..80ada61f868 100644 --- a/apps/browser/src/popup/scss/base.scss +++ b/apps/browser/src/popup/scss/base.scss @@ -8,6 +8,34 @@ html { overflow: hidden; + min-height: 600px; + height: 100%; + + &.body-sm { + min-height: 500px; + } + + &.body-xs { + min-height: 400px; + } + + &.body-xxs { + min-height: 300px; + } + + &.body-3xs { + min-height: 240px; + } + + &.body-full { + min-height: unset; + width: 100%; + height: 100%; + + & body { + width: 100%; + } + } } html, @@ -20,9 +48,9 @@ body { body { width: 380px; - height: 600px; + height: 100%; position: relative; - min-height: 100vh; + min-height: inherit; overflow: hidden; color: $text-color; background-color: $background-color; @@ -31,23 +59,6 @@ body { color: themed("textColor"); background-color: themed("backgroundColor"); } - - &.body-sm { - height: 500px; - } - - &.body-xs { - height: 400px; - } - - &.body-xxs { - height: 300px; - } - - &.body-full { - width: 100%; - height: 100%; - } } h1, diff --git a/apps/browser/src/popup/scss/misc.scss b/apps/browser/src/popup/scss/misc.scss index 8aace90d0a6..006e1d35f6a 100644 --- a/apps/browser/src/popup/scss/misc.scss +++ b/apps/browser/src/popup/scss/misc.scss @@ -211,12 +211,6 @@ p.lead { } } -#hcaptcha_iframe { - width: 100%; - border: none; - transition: height 0.25s linear; -} - body.linux-webauthn { width: 485px !important; #web-authn-frame { diff --git a/apps/browser/src/popup/scss/variables.scss b/apps/browser/src/popup/scss/variables.scss index b78f06f2f3f..aea69e26436 100644 --- a/apps/browser/src/popup/scss/variables.scss +++ b/apps/browser/src/popup/scss/variables.scss @@ -1,6 +1,6 @@ $dark-icon-themes: "theme_dark"; -$font-family-sans-serif: "DM Sans", "Helvetica Neue", Helvetica, Arial, sans-serif; +$font-family-sans-serif: Roboto, "Helvetica Neue", Helvetica, Arial, sans-serif; $font-family-monospace: Menlo, Monaco, Consolas, "Courier New", monospace; $font-size-base: 16px; $font-size-large: 18px; diff --git a/apps/browser/src/popup/services/services.module.ts b/apps/browser/src/popup/services/services.module.ts index dad4e887a12..6ede88dfc13 100644 --- a/apps/browser/src/popup/services/services.module.ts +++ b/apps/browser/src/popup/services/services.module.ts @@ -5,9 +5,9 @@ import { Router } from "@angular/router"; import { merge, of, Subject } from "rxjs"; import { CollectionService } from "@bitwarden/admin-console/common"; -import { ViewCacheService } from "@bitwarden/angular/platform/abstractions/view-cache.service"; import { AngularThemingService } from "@bitwarden/angular/platform/services/theming/angular-theming.service"; import { SafeProvider, safeProvider } from "@bitwarden/angular/platform/utils/safe-provider"; +import { ViewCacheService } from "@bitwarden/angular/platform/view-cache"; import { CLIENT_TYPE, DEFAULT_VAULT_TIMEOUT, @@ -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..3d93f5d4e04 100644 --- a/apps/browser/src/popup/tabs-v2.component.ts +++ b/apps/browser/src/popup/tabs-v2.component.ts @@ -1,11 +1,62 @@ import { Component } from "@angular/core"; +import { combineLatest, map, Observable, startWith, switchMap } from "rxjs"; + +import { NudgesService } from "@bitwarden/angular/vault"; +import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; +import { getUserId } from "@bitwarden/common/auth/services/account.service"; +import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum"; +import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; +import { Icons } from "@bitwarden/components"; + +import { NavButton } from "../platform/popup/layout/popup-tab-navigation.component"; @Component({ selector: "app-tabs-v2", - template: ` - - - - `, + templateUrl: "./tabs-v2.component.html", + standalone: false, }) -export class TabsV2Component {} +export class TabsV2Component { + private hasActiveBadges$ = this.accountService.activeAccount$ + .pipe(getUserId) + .pipe(switchMap((userId) => this.nudgesService.hasActiveBadges$(userId))); + protected navButtons$: Observable = combineLatest([ + this.configService.getFeatureFlag$(FeatureFlag.PM8851_BrowserOnboardingNudge), + this.hasActiveBadges$, + ]).pipe( + startWith([false, false]), + map(([onboardingFeatureEnabled, hasBadges]) => { + return [ + { + label: "vault", + page: "/tabs/vault", + icon: Icons.VaultInactive, + iconActive: Icons.VaultActive, + }, + { + label: "generator", + page: "/tabs/generator", + icon: Icons.GeneratorInactive, + iconActive: Icons.GeneratorActive, + }, + { + label: "send", + page: "/tabs/send", + icon: Icons.SendInactive, + iconActive: Icons.SendActive, + }, + { + label: "settings", + page: "/tabs/settings", + icon: Icons.SettingsInactive, + iconActive: Icons.SettingsActive, + showBerry: onboardingFeatureEnabled && hasBadges, + }, + ]; + }), + ); + constructor( + private nudgesService: NudgesService, + private accountService: AccountService, + private readonly configService: ConfigService, + ) {} +} 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/components/file-popout-callout.component.ts b/apps/browser/src/tools/popup/components/file-popout-callout.component.ts index 491e33c5738..e30fbf58321 100644 --- a/apps/browser/src/tools/popup/components/file-popout-callout.component.ts +++ b/apps/browser/src/tools/popup/components/file-popout-callout.component.ts @@ -12,7 +12,6 @@ import { FilePopoutUtilsService } from "../services/file-popout-utils.service"; @Component({ selector: "tools-file-popout-callout", templateUrl: "file-popout-callout.component.html", - standalone: true, imports: [CommonModule, JslibModule, CalloutModule], }) export class FilePopoutCalloutComponent implements OnInit { diff --git a/apps/browser/src/tools/popup/generator/credential-generator-history.component.ts b/apps/browser/src/tools/popup/generator/credential-generator-history.component.ts index 2bf290b3223..441e5d6e4c6 100644 --- a/apps/browser/src/tools/popup/generator/credential-generator-history.component.ts +++ b/apps/browser/src/tools/popup/generator/credential-generator-history.component.ts @@ -28,7 +28,6 @@ import { PopupPageComponent } from "../../../platform/popup/layout/popup-page.co @Component({ selector: "app-credential-generator-history", templateUrl: "credential-generator-history.component.html", - standalone: true, imports: [ ButtonModule, CommonModule, diff --git a/apps/browser/src/tools/popup/generator/credential-generator.component.ts b/apps/browser/src/tools/popup/generator/credential-generator.component.ts index 9c1af07efdd..b34c829b006 100644 --- a/apps/browser/src/tools/popup/generator/credential-generator.component.ts +++ b/apps/browser/src/tools/popup/generator/credential-generator.component.ts @@ -7,12 +7,10 @@ import { GeneratorModule } from "@bitwarden/generator-components"; import { CurrentAccountComponent } from "../../../auth/popup/account-switching/current-account.component"; import { PopOutComponent } from "../../../platform/popup/components/pop-out.component"; -import { PopupFooterComponent } from "../../../platform/popup/layout/popup-footer.component"; import { PopupHeaderComponent } from "../../../platform/popup/layout/popup-header.component"; import { PopupPageComponent } from "../../../platform/popup/layout/popup-page.component"; @Component({ - standalone: true, selector: "credential-generator", templateUrl: "credential-generator.component.html", imports: [ @@ -22,7 +20,6 @@ import { PopupPageComponent } from "../../../platform/popup/layout/popup-page.co PopOutComponent, PopupHeaderComponent, PopupPageComponent, - PopupFooterComponent, RouterModule, ItemModule, ], diff --git a/apps/browser/src/tools/popup/send-v2/add-edit/send-add-edit.component.ts b/apps/browser/src/tools/popup/send-v2/add-edit/send-add-edit.component.ts index 0962dec3dcf..b6957248d75 100644 --- a/apps/browser/src/tools/popup/send-v2/add-edit/send-add-edit.component.ts +++ b/apps/browser/src/tools/popup/send-v2/add-edit/send-add-edit.component.ts @@ -63,7 +63,6 @@ export type AddEditQueryParams = Partial>; @Component({ selector: "tools-send-add-edit", templateUrl: "send-add-edit.component.html", - standalone: true, providers: [{ provide: SendFormConfigService, useClass: DefaultSendFormConfigService }], imports: [ CommonModule, diff --git a/apps/browser/src/tools/popup/send-v2/send-created/send-created.component.ts b/apps/browser/src/tools/popup/send-v2/send-created/send-created.component.ts index 7191040ac6f..89d1ad5e809 100644 --- a/apps/browser/src/tools/popup/send-v2/send-created/send-created.component.ts +++ b/apps/browser/src/tools/popup/send-v2/send-created/send-created.component.ts @@ -3,7 +3,7 @@ import { CommonModule } from "@angular/common"; import { Component } from "@angular/core"; import { takeUntilDestroyed } from "@angular/core/rxjs-interop"; -import { ActivatedRoute, Router, RouterLink, RouterModule } from "@angular/router"; +import { ActivatedRoute, Router, RouterModule } from "@angular/router"; import { firstValueFrom } from "rxjs"; import { JslibModule } from "@bitwarden/angular/jslib.module"; @@ -23,7 +23,6 @@ import { PopupPageComponent } from "../../../../platform/popup/layout/popup-page @Component({ selector: "app-send-created", templateUrl: "./send-created.component.html", - standalone: true, imports: [ ButtonModule, CommonModule, @@ -31,7 +30,6 @@ import { PopupPageComponent } from "../../../../platform/popup/layout/popup-page PopOutComponent, PopupHeaderComponent, PopupPageComponent, - RouterLink, RouterModule, PopupFooterComponent, IconModule, diff --git a/apps/browser/src/tools/popup/send-v2/send-file-popout-dialog/send-file-popout-dialog-container.component.ts b/apps/browser/src/tools/popup/send-v2/send-file-popout-dialog/send-file-popout-dialog-container.component.ts index 4266dd3914e..251f19cf252 100644 --- a/apps/browser/src/tools/popup/send-v2/send-file-popout-dialog/send-file-popout-dialog-container.component.ts +++ b/apps/browser/src/tools/popup/send-v2/send-file-popout-dialog/send-file-popout-dialog-container.component.ts @@ -15,7 +15,6 @@ import { SendFilePopoutDialogComponent } from "./send-file-popout-dialog.compone @Component({ selector: "send-file-popout-dialog-container", templateUrl: "./send-file-popout-dialog-container.component.html", - standalone: true, imports: [JslibModule, CommonModule], }) export class SendFilePopoutDialogContainerComponent implements OnInit { diff --git a/apps/browser/src/tools/popup/send-v2/send-file-popout-dialog/send-file-popout-dialog.component.ts b/apps/browser/src/tools/popup/send-v2/send-file-popout-dialog/send-file-popout-dialog.component.ts index fb21b5bb026..248b3c49a98 100644 --- a/apps/browser/src/tools/popup/send-v2/send-file-popout-dialog/send-file-popout-dialog.component.ts +++ b/apps/browser/src/tools/popup/send-v2/send-file-popout-dialog/send-file-popout-dialog.component.ts @@ -9,7 +9,6 @@ import BrowserPopupUtils from "../../../../platform/popup/browser-popup-utils"; @Component({ selector: "send-file-popout-dialog", templateUrl: "./send-file-popout-dialog.component.html", - standalone: true, imports: [JslibModule, CommonModule, DialogModule, ButtonModule, TypographyModule], }) export class SendFilePopoutDialogComponent { diff --git a/apps/browser/src/tools/popup/send-v2/send-v2.component.html b/apps/browser/src/tools/popup/send-v2/send-v2.component.html index d51bda45b55..082112a86ab 100644 --- a/apps/browser/src/tools/popup/send-v2/send-v2.component.html +++ b/apps/browser/src/tools/popup/send-v2/send-v2.component.html @@ -21,12 +21,13 @@ class="tw-flex tw-flex-col tw-h-full tw-justify-center" > - {{ "sendsNoItemsTitle" | i18n }} - {{ "sendsNoItemsMessage" | i18n }} + {{ "sendsTitleNoItems" | i18n }} + {{ "sendsBodyNoItems" | i18n }}
diff --git a/apps/browser/src/tools/popup/send-v2/send-v2.component.ts b/apps/browser/src/tools/popup/send-v2/send-v2.component.ts index 49804abda5d..2fca3e41f88 100644 --- a/apps/browser/src/tools/popup/send-v2/send-v2.component.ts +++ b/apps/browser/src/tools/popup/send-v2/send-v2.component.ts @@ -1,7 +1,6 @@ import { CommonModule } from "@angular/common"; -import { Component, OnDestroy, OnInit } from "@angular/core"; +import { Component, OnDestroy } from "@angular/core"; import { takeUntilDestroyed } from "@angular/core/rxjs-interop"; -import { RouterLink } from "@angular/router"; import { combineLatest, switchMap } from "rxjs"; import { JslibModule } from "@bitwarden/angular/jslib.module"; @@ -12,13 +11,13 @@ import { getUserId } from "@bitwarden/common/auth/services/account.service"; import { SendType } from "@bitwarden/common/tools/send/enums/send-type"; import { ButtonModule, CalloutModule, Icons, NoItemsModule } from "@bitwarden/components"; import { - NoSendsIcon, NewSendDropdownComponent, - SendListItemsContainerComponent, + NoSendsIcon, SendItemsService, - SendSearchComponent, SendListFiltersComponent, SendListFiltersService, + SendListItemsContainerComponent, + SendSearchComponent, } from "@bitwarden/send-ui"; import { CurrentAccountComponent } from "../../../auth/popup/account-switching/current-account.component"; @@ -26,6 +25,8 @@ import { PopOutComponent } from "../../../platform/popup/components/pop-out.comp import { PopupHeaderComponent } from "../../../platform/popup/layout/popup-header.component"; import { PopupPageComponent } from "../../../platform/popup/layout/popup-page.component"; +// FIXME: update to use a const object instead of a typescript enum +// eslint-disable-next-line @bitwarden/platform/no-enums export enum SendState { Empty, NoResults, @@ -33,7 +34,6 @@ export enum SendState { @Component({ templateUrl: "send-v2.component.html", - standalone: true, imports: [ CalloutModule, PopupPageComponent, @@ -44,14 +44,13 @@ export enum SendState { JslibModule, CommonModule, ButtonModule, - RouterLink, NewSendDropdownComponent, SendListItemsContainerComponent, SendListFiltersComponent, SendSearchComponent, ], }) -export class SendV2Component implements OnInit, OnDestroy { +export class SendV2Component implements OnDestroy { sendType = SendType; sendState = SendState; @@ -109,7 +108,5 @@ export class SendV2Component implements OnInit, OnDestroy { }); } - ngOnInit(): void {} - ngOnDestroy(): void {} } diff --git a/apps/browser/src/tools/popup/settings/about-dialog/about-dialog.component.html b/apps/browser/src/tools/popup/settings/about-dialog/about-dialog.component.html index 40dad4cde4b..af68959fe5d 100644 --- a/apps/browser/src/tools/popup/settings/about-dialog/about-dialog.component.html +++ b/apps/browser/src/tools/popup/settings/about-dialog/about-dialog.component.html @@ -6,7 +6,7 @@

© Bitwarden Inc. 2015-{{ year }}

-
+

{{ "version" | i18n }}: {{ version$ | async }}

SDK: {{ sdkVersion$ | async }}

diff --git a/apps/browser/src/tools/popup/settings/about-dialog/about-dialog.component.ts b/apps/browser/src/tools/popup/settings/about-dialog/about-dialog.component.ts index 6f1c1162eb4..39bff089668 100644 --- a/apps/browser/src/tools/popup/settings/about-dialog/about-dialog.component.ts +++ b/apps/browser/src/tools/popup/settings/about-dialog/about-dialog.component.ts @@ -18,7 +18,6 @@ import { @Component({ templateUrl: "about-dialog.component.html", - standalone: true, imports: [CommonModule, JslibModule, DialogModule, ButtonModule, TypographyModule], }) export class AboutDialogComponent { diff --git a/apps/browser/src/tools/popup/settings/about-page/about-page-v2.component.html b/apps/browser/src/tools/popup/settings/about-page/about-page-v2.component.html index bcc0f12d0d7..839681889a8 100644 --- a/apps/browser/src/tools/popup/settings/about-page/about-page-v2.component.html +++ b/apps/browser/src/tools/popup/settings/about-page/about-page-v2.component.html @@ -23,7 +23,7 @@ - + {{ "moreFromBitwarden" | i18n }} diff --git a/apps/browser/src/tools/popup/settings/about-page/about-page-v2.component.ts b/apps/browser/src/tools/popup/settings/about-page/about-page-v2.component.ts index 1d0d4218439..8a76290eff1 100644 --- a/apps/browser/src/tools/popup/settings/about-page/about-page-v2.component.ts +++ b/apps/browser/src/tools/popup/settings/about-page/about-page-v2.component.ts @@ -5,6 +5,8 @@ import { firstValueFrom } from "rxjs"; import { JslibModule } from "@bitwarden/angular/jslib.module"; import { DeviceType } from "@bitwarden/common/enums"; +import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum"; +import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; import { EnvironmentService } from "@bitwarden/common/platform/abstractions/environment.service"; import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; import { DialogService, ItemModule } from "@bitwarden/components"; @@ -31,7 +33,6 @@ const RateUrls = { @Component({ templateUrl: "about-page-v2.component.html", - standalone: true, imports: [ CommonModule, JslibModule, @@ -47,12 +48,17 @@ export class AboutPageV2Component { private dialogService: DialogService, private environmentService: EnvironmentService, private platformUtilsService: PlatformUtilsService, + private configService: ConfigService, ) {} about() { this.dialogService.open(AboutDialogComponent); } + protected isNudgeFeatureEnabled$ = this.configService.getFeatureFlag$( + FeatureFlag.PM8851_BrowserOnboardingNudge, + ); + async launchHelp() { const confirmed = await this.dialogService.openSimpleDialog({ title: { key: "continueToHelpCenter" }, diff --git a/apps/browser/src/tools/popup/settings/export/export-browser-v2.component.ts b/apps/browser/src/tools/popup/settings/export/export-browser-v2.component.ts index 27147b75d39..5aebee3b781 100644 --- a/apps/browser/src/tools/popup/settings/export/export-browser-v2.component.ts +++ b/apps/browser/src/tools/popup/settings/export/export-browser-v2.component.ts @@ -14,7 +14,6 @@ import { PopupPageComponent } from "../../../../platform/popup/layout/popup-page @Component({ templateUrl: "export-browser-v2.component.html", - standalone: true, imports: [ CommonModule, JslibModule, diff --git a/apps/browser/src/tools/popup/settings/import/import-browser-v2.component.ts b/apps/browser/src/tools/popup/settings/import/import-browser-v2.component.ts index 1c5558bd90e..506dae2fb18 100644 --- a/apps/browser/src/tools/popup/settings/import/import-browser-v2.component.ts +++ b/apps/browser/src/tools/popup/settings/import/import-browser-v2.component.ts @@ -13,7 +13,6 @@ import { PopupPageComponent } from "../../../../platform/popup/layout/popup-page @Component({ templateUrl: "import-browser-v2.component.html", - standalone: true, imports: [ CommonModule, JslibModule, 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..0b2e84712a4 100644 --- a/apps/browser/src/tools/popup/settings/settings-v2.component.html +++ b/apps/browser/src/tools/popup/settings/settings-v2.component.html @@ -17,7 +17,16 @@ - {{ "autofill" | i18n }} +
+

{{ "autofill" | i18n }}

+ 1 +
@@ -29,15 +38,33 @@
- + - {{ "vault" | i18n }} +
+

{{ "settingsVaultOptions" | i18n }}

+ + 1 +
+
- + {{ "appearance" | i18n }} @@ -49,5 +76,28 @@ + + + +
+

{{ "downloadBitwardenOnAllDevices" | i18n }}

+ 1 + +
+ +
+
+ + + + {{ "moreFromBitwarden" | i18n }} + + + 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..a0383b99390 100644 --- a/apps/browser/src/tools/popup/settings/settings-v2.component.ts +++ b/apps/browser/src/tools/popup/settings/settings-v2.component.ts @@ -1,18 +1,33 @@ import { CommonModule } from "@angular/common"; -import { Component } from "@angular/core"; +import { Component, OnInit } from "@angular/core"; import { RouterModule } from "@angular/router"; +import { + combineLatest, + filter, + firstValueFrom, + map, + Observable, + shareReplay, + switchMap, +} from "rxjs"; import { JslibModule } from "@bitwarden/angular/jslib.module"; -import { ItemModule } from "@bitwarden/components"; +import { NudgesService, NudgeType } from "@bitwarden/angular/vault"; +import { Account, AccountService } from "@bitwarden/common/auth/abstractions/account.service"; +import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum"; +import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; +import { UserId } from "@bitwarden/common/types/guid"; +import { BadgeComponent, ItemModule } from "@bitwarden/components"; import { CurrentAccountComponent } from "../../../auth/popup/account-switching/current-account.component"; +import { AutofillBrowserSettingsService } from "../../../autofill/services/autofill-browser-settings.service"; +import { BrowserApi } from "../../../platform/browser/browser-api"; 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"; @Component({ templateUrl: "settings-v2.component.html", - standalone: true, imports: [ CommonModule, JslibModule, @@ -22,6 +37,66 @@ import { PopupPageComponent } from "../../../platform/popup/layout/popup-page.co PopOutComponent, ItemModule, CurrentAccountComponent, + BadgeComponent, ], }) -export class SettingsV2Component {} +export class SettingsV2Component implements OnInit { + NudgeType = NudgeType; + activeUserId: UserId | null = null; + protected isBrowserAutofillSettingOverridden = false; + + private authenticatedAccount$: Observable = this.accountService.activeAccount$.pipe( + filter((account): account is Account => account !== null), + shareReplay({ bufferSize: 1, refCount: true }), + ); + + showDownloadBitwardenNudge$: Observable = this.authenticatedAccount$.pipe( + switchMap((account) => + this.nudgesService.showNudgeBadge$(NudgeType.DownloadBitwarden, account.id), + ), + ); + + showVaultBadge$: Observable = this.authenticatedAccount$.pipe( + switchMap((account) => + this.nudgesService.showNudgeBadge$(NudgeType.EmptyVaultNudge, account.id), + ), + ); + + showAutofillBadge$: Observable = combineLatest([ + this.autofillBrowserSettingsService.defaultBrowserAutofillDisabled$, + this.authenticatedAccount$, + ]).pipe( + switchMap(([defaultBrowserAutofillDisabled, account]) => + this.nudgesService.showNudgeBadge$(NudgeType.AutofillNudge, account.id).pipe( + map((badgeStatus) => { + return !defaultBrowserAutofillDisabled && badgeStatus; + }), + ), + ), + ); + + protected isNudgeFeatureEnabled$ = this.configService.getFeatureFlag$( + FeatureFlag.PM8851_BrowserOnboardingNudge, + ); + + constructor( + private readonly nudgesService: NudgesService, + private readonly accountService: AccountService, + private readonly autofillBrowserSettingsService: AutofillBrowserSettingsService, + private readonly configService: ConfigService, + ) {} + + async ngOnInit() { + this.isBrowserAutofillSettingOverridden = + await this.autofillBrowserSettingsService.isBrowserAutofillSettingOverridden( + BrowserApi.getBrowserClientVendor(window), + ); + } + + async dismissBadge(type: NudgeType) { + if (await firstValueFrom(this.showVaultBadge$)) { + const account = await firstValueFrom(this.authenticatedAccount$); + await this.nudgesService.dismissNudge(type, account.id as UserId, true); + } + } +} diff --git a/apps/browser/src/vault/guards/at-risk-passwords.guard.ts b/apps/browser/src/vault/guards/at-risk-passwords.guard.ts index 6bcdddfde81..fc302dd6c36 100644 --- a/apps/browser/src/vault/guards/at-risk-passwords.guard.ts +++ b/apps/browser/src/vault/guards/at-risk-passwords.guard.ts @@ -1,6 +1,6 @@ import { inject } from "@angular/core"; -import { CanActivateFn } from "@angular/router"; -import { switchMap, tap } from "rxjs"; +import { CanActivateFn, Router } from "@angular/router"; +import { map, switchMap } from "rxjs"; import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; @@ -13,18 +13,22 @@ export const canAccessAtRiskPasswords: CanActivateFn = () => { const taskService = inject(TaskService); const toastService = inject(ToastService); const i18nService = inject(I18nService); + const router = inject(Router); return accountService.activeAccount$.pipe( filterOutNullish(), switchMap((user) => taskService.tasksEnabled$(user.id)), - tap((tasksEnabled) => { + map((tasksEnabled) => { if (!tasksEnabled) { toastService.showToast({ variant: "error", title: "", - message: i18nService.t("accessDenied"), + message: i18nService.t("noPermissionsViewPage"), }); + + return router.createUrlTree(["/tabs/vault"]); } + return 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..18482706272 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"; @@ -11,7 +11,6 @@ import { I18nPipe } from "@bitwarden/ui-common"; @Component({ selector: "vault-at-risk-password-callout", - standalone: true, imports: [CommonModule, AnchorLinkDirective, RouterModule, CalloutModule, I18nPipe], templateUrl: "./at-risk-password-callout.component.html", }) @@ -20,21 +19,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-carousel-dialog/at-risk-carousel-dialog.component.ts b/apps/browser/src/vault/popup/components/at-risk-carousel-dialog/at-risk-carousel-dialog.component.ts index fcca125c2b6..08c466d21a9 100644 --- a/apps/browser/src/vault/popup/components/at-risk-carousel-dialog/at-risk-carousel-dialog.component.ts +++ b/apps/browser/src/vault/popup/components/at-risk-carousel-dialog/at-risk-carousel-dialog.component.ts @@ -1,5 +1,6 @@ import { Component, inject, signal } from "@angular/core"; +import { UnionOfValues } from "@bitwarden/common/vault/types/union-of-values"; import { DialogRef, ButtonModule, @@ -10,9 +11,11 @@ import { import { I18nPipe } from "@bitwarden/ui-common"; import { DarkImageSourceDirective, VaultCarouselModule } from "@bitwarden/vault"; -export enum AtRiskCarouselDialogResult { - Dismissed = "dismissed", -} +export const AtRiskCarouselDialogResult = { + Dismissed: "dismissed", +} as const; + +type AtRiskCarouselDialogResult = UnionOfValues; @Component({ selector: "vault-at-risk-carousel-dialog", @@ -25,7 +28,6 @@ export enum AtRiskCarouselDialogResult { DarkImageSourceDirective, I18nPipe, ], - standalone: true, }) export class AtRiskCarouselDialogComponent { private dialogRef = inject(DialogRef); 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..dae00ba6c2b 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 { @@ -32,7 +35,6 @@ import { AtRiskPasswordPageService } from "./at-risk-password-page.service"; import { AtRiskPasswordsComponent } from "./at-risk-passwords.component"; @Component({ - standalone: true, selector: "popup-header", template: ``, }) @@ -42,7 +44,6 @@ class MockPopupHeaderComponent { } @Component({ - standalone: true, selector: "popup-page", template: ``, }) @@ -51,7 +52,6 @@ class MockPopupPageComponent { } @Component({ - standalone: true, selector: "app-vault-icon", template: ``, }) @@ -66,6 +66,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 +74,7 @@ describe("AtRiskPasswordsComponent", () => { const mockAtRiskPasswordPageService = mock(); const mockChangeLoginPasswordService = mock(); const mockDialogService = mock(); + const mockConfigService = mock(); beforeEach(async () => { mockTasks$ = new BehaviorSubject([ @@ -101,6 +103,7 @@ describe("AtRiskPasswordsComponent", () => { name: "Org 1", } as Organization, ]); + mockNotifications$ = new BehaviorSubject([]); mockInlineMenuVisibility$ = new BehaviorSubject( AutofillOverlayVisibility.Off, @@ -110,6 +113,7 @@ describe("AtRiskPasswordsComponent", () => { setInlineMenuVisibility.mockClear(); mockToastService.showToast.mockClear(); mockDialogService.open.mockClear(); + mockConfigService.getFeatureFlag.mockClear(); mockAtRiskPasswordPageService.isCalloutDismissed.mockReturnValue(calloutDismissed$); await TestBed.configureTestingModule({ @@ -133,6 +137,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 +155,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..dc6712aa23f 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 { @@ -64,7 +79,6 @@ import { AtRiskPasswordPageService } from "./at-risk-password-page.service"; { provide: ChangeLoginPasswordService, useClass: DefaultChangeLoginPasswordService }, ], selector: "vault-at-risk-passwords", - standalone: true, templateUrl: "./at-risk-passwords.component.html", }) export class AtRiskPasswordsComponent implements OnInit { @@ -81,6 +95,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 +197,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/add-edit/add-edit-v2.component.spec.ts b/apps/browser/src/vault/popup/components/vault-v2/add-edit/add-edit-v2.component.spec.ts index 6974e6f7359..be772fa6ee5 100644 --- a/apps/browser/src/vault/popup/components/vault-v2/add-edit/add-edit-v2.component.spec.ts +++ b/apps/browser/src/vault/popup/components/vault-v2/add-edit/add-edit-v2.component.spec.ts @@ -51,6 +51,7 @@ describe("AddEditV2Component", () => { const disable = jest.fn(); const navigate = jest.fn(); const back = jest.fn().mockResolvedValue(null); + const setHistory = jest.fn(); const collect = jest.fn().mockResolvedValue(null); beforeEach(async () => { @@ -70,7 +71,7 @@ describe("AddEditV2Component", () => { providers: [ { provide: PlatformUtilsService, useValue: mock() }, { provide: ConfigService, useValue: mock() }, - { provide: PopupRouterCacheService, useValue: { back } }, + { provide: PopupRouterCacheService, useValue: { back, setHistory } }, { provide: PopupCloseWarningService, useValue: { disable } }, { provide: Router, useValue: { navigate } }, { provide: ActivatedRoute, useValue: { queryParams: queryParams$ } }, diff --git a/apps/browser/src/vault/popup/components/vault-v2/add-edit/add-edit-v2.component.ts b/apps/browser/src/vault/popup/components/vault-v2/add-edit/add-edit-v2.component.ts index 30fd57a6bc6..5aac720738a 100644 --- a/apps/browser/src/vault/popup/components/vault-v2/add-edit/add-edit-v2.component.ts +++ b/apps/browser/src/vault/popup/components/vault-v2/add-edit/add-edit-v2.component.ts @@ -14,9 +14,10 @@ import { getUserId } from "@bitwarden/common/auth/services/account.service"; import { EventType } from "@bitwarden/common/enums"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; +import { Utils } from "@bitwarden/common/platform/misc/utils"; 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 { CipherType, toCipherType } 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 { AddEditCipherInfo } from "@bitwarden/common/vault/types/add-edit-cipher-info"; @@ -40,6 +41,7 @@ import { } from "@bitwarden/vault"; import { BrowserFido2UserInterfaceSession } from "../../../../../autofill/fido2/services/browser-fido2-user-interface.service"; +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 { PopupFooterComponent } from "../../../../../platform/popup/layout/popup-footer.component"; @@ -62,7 +64,7 @@ import { OpenAttachmentsComponent } from "../attachments/open-attachments/open-a class QueryParams { constructor(params: Params) { this.cipherId = params.cipherId; - this.type = params.type != undefined ? parseInt(params.type, null) : undefined; + this.type = toCipherType(params.type); this.clone = params.clone === "true"; this.folderId = params.folderId; this.organizationId = params.organizationId; @@ -70,6 +72,7 @@ class QueryParams { this.uri = params.uri; this.username = params.username; this.name = params.name; + this.prefillNameAndURIFromTab = params.prefillNameAndURIFromTab; } /** @@ -116,6 +119,12 @@ class QueryParams { * Optional name to pre-fill for the cipher. */ name?: string; + + /** + * Optional flag to pre-fill the name and URI from the current tab. + * NOTE: This will override the `uri` and `name` query parameters if set to true. + */ + prefillNameAndURIFromTab?: true; } export type AddEditQueryParams = Partial>; @@ -123,7 +132,6 @@ export type AddEditQueryParams = Partial>; @Component({ selector: "app-add-edit-v2", templateUrl: "add-edit-v2.component.html", - standalone: true, providers: [ { provide: CipherFormConfigService, useClass: DefaultCipherFormConfigService }, { provide: TotpCaptureService, useClass: BrowserTotpCaptureService }, @@ -257,6 +265,8 @@ export class AddEditV2Component implements OnInit { replaceUrl: true, queryParams: { cipherId: cipher.id }, }); + // Clear popup history so after closing/reopening, Back won’t return to the add-edit form + await this.popupRouterCacheService.setHistory([]); } } @@ -281,8 +291,7 @@ export class AddEditV2Component implements OnInit { if (config.mode === "edit" && !config.originalCipher.edit) { config.mode = "partial-edit"; } - - config.initialValues = this.setInitialValuesFromParams(params); + config.initialValues = await this.setInitialValuesFromParams(params); const activeUserId = await firstValueFrom( this.accountService.activeAccount$.pipe(getUserId), @@ -326,7 +335,7 @@ export class AddEditV2Component implements OnInit { }); } - setInitialValuesFromParams(params: QueryParams) { + async setInitialValuesFromParams(params: QueryParams) { const initialValues = {} as OptionalInitialValues; if (params.folderId) { initialValues.folderId = params.folderId; @@ -346,6 +355,14 @@ export class AddEditV2Component implements OnInit { if (params.name) { initialValues.name = params.name; } + + if (params.prefillNameAndURIFromTab) { + const tab = await BrowserApi.getTabFromCurrentWindow(); + + initialValues.loginUri = tab.url; + initialValues.name = Utils.getHostname(tab.url); + } + return initialValues; } diff --git a/apps/browser/src/vault/popup/components/vault-v2/assign-collections/assign-collections.component.ts b/apps/browser/src/vault/popup/components/vault-v2/assign-collections/assign-collections.component.ts index 27f3b7e5e18..8374cc254a9 100644 --- a/apps/browser/src/vault/popup/components/vault-v2/assign-collections/assign-collections.component.ts +++ b/apps/browser/src/vault/popup/components/vault-v2/assign-collections/assign-collections.component.ts @@ -11,7 +11,6 @@ import { CollectionService } from "@bitwarden/admin-console/common"; import { JslibModule } from "@bitwarden/angular/jslib.module"; import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; import { OrganizationId } from "@bitwarden/common/types/guid"; -import { OrgKey, UserKey } from "@bitwarden/common/types/key"; import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service"; import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view"; import { @@ -29,7 +28,6 @@ import { PopupHeaderComponent } from "../../../../../platform/popup/layout/popup import { PopupPageComponent } from "../../../../../platform/popup/layout/popup-page.component"; @Component({ - standalone: true, selector: "app-assign-collections", templateUrl: "./assign-collections.component.html", imports: [ @@ -66,11 +64,7 @@ export class AssignCollections { route.queryParams.pipe( switchMap(async ({ cipherId }) => { const cipherDomain = await this.cipherService.get(cipherId, userId); - const key: UserKey | OrgKey = await this.cipherService.getKeyForCipherKeyDecryption( - cipherDomain, - userId, - ); - return cipherDomain.decrypt(key); + return await this.cipherService.decrypt(cipherDomain, userId); }), ), ), @@ -79,7 +73,7 @@ export class AssignCollections { combineLatest([cipher$, this.collectionService.decryptedCollections$]) .pipe(takeUntilDestroyed(), first()) .subscribe(([cipherView, collections]) => { - let availableCollections = collections.filter((c) => !c.readOnly); + let availableCollections = collections; const organizationId = (cipherView?.organizationId as OrganizationId) ?? null; // If the cipher is already a part of an organization, 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..6e4215c1ec2 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"; @@ -24,7 +26,6 @@ import { PopupRouterCacheService } from "../../../../../platform/popup/view-cach import { AttachmentsV2Component } from "./attachments-v2.component"; @Component({ - standalone: true, selector: "popup-header", template: ``, }) @@ -34,7 +35,6 @@ class MockPopupHeaderComponent { } @Component({ - standalone: true, selector: "popup-footer", template: ``, }) @@ -77,6 +77,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/attachments/attachments-v2.component.ts b/apps/browser/src/vault/popup/components/vault-v2/attachments/attachments-v2.component.ts index 32d446daf75..fc6d882dfd5 100644 --- a/apps/browser/src/vault/popup/components/vault-v2/attachments/attachments-v2.component.ts +++ b/apps/browser/src/vault/popup/components/vault-v2/attachments/attachments-v2.component.ts @@ -18,7 +18,6 @@ import { PopupPageComponent } from "../../../../../platform/popup/layout/popup-p import { PopupRouterCacheService } from "../../../../../platform/popup/view-cache/popup-router-cache.service"; @Component({ - standalone: true, selector: "app-attachments-v2", templateUrl: "./attachments-v2.component.html", imports: [ diff --git a/apps/browser/src/vault/popup/components/vault-v2/attachments/open-attachments/open-attachments.component.spec.ts b/apps/browser/src/vault/popup/components/vault-v2/attachments/open-attachments/open-attachments.component.spec.ts index 66d9096cd5c..ec5c93feb9e 100644 --- a/apps/browser/src/vault/popup/components/vault-v2/attachments/open-attachments/open-attachments.component.spec.ts +++ b/apps/browser/src/vault/popup/components/vault-v2/attachments/open-attachments/open-attachments.component.spec.ts @@ -81,6 +81,7 @@ describe("OpenAttachmentsComponent", () => { useValue: { get: getCipher, getKeyForCipherKeyDecryption: () => Promise.resolve(null), + decrypt: jest.fn().mockResolvedValue(cipherView), }, }, { diff --git a/apps/browser/src/vault/popup/components/vault-v2/attachments/open-attachments/open-attachments.component.ts b/apps/browser/src/vault/popup/components/vault-v2/attachments/open-attachments/open-attachments.component.ts index 1bc7e22e6d5..6577975ae0c 100644 --- a/apps/browser/src/vault/popup/components/vault-v2/attachments/open-attachments/open-attachments.component.ts +++ b/apps/browser/src/vault/popup/components/vault-v2/attachments/open-attachments/open-attachments.component.ts @@ -18,22 +18,15 @@ import { ProductTierType } from "@bitwarden/common/billing/enums"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { CipherId } from "@bitwarden/common/types/guid"; import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service"; -import { - BadgeModule, - CardComponent, - ItemModule, - ToastService, - TypographyModule, -} from "@bitwarden/components"; +import { BadgeModule, ItemModule, ToastService, TypographyModule } from "@bitwarden/components"; import BrowserPopupUtils from "../../../../../../platform/popup/browser-popup-utils"; import { FilePopoutUtilsService } from "../../../../../../tools/popup/services/file-popout-utils.service"; @Component({ - standalone: true, selector: "app-open-attachments", templateUrl: "./open-attachments.component.html", - imports: [BadgeModule, CommonModule, ItemModule, JslibModule, TypographyModule, CardComponent], + imports: [BadgeModule, CommonModule, ItemModule, JslibModule, TypographyModule], }) export class OpenAttachmentsComponent implements OnInit { /** Cipher `id` */ @@ -81,9 +74,7 @@ export class OpenAttachmentsComponent implements OnInit { this.accountService.activeAccount$.pipe(map((a) => a?.id)), ); const cipherDomain = await this.cipherService.get(this.cipherId, activeUserId); - const cipher = await cipherDomain.decrypt( - await this.cipherService.getKeyForCipherKeyDecryption(cipherDomain, activeUserId), - ); + const cipher = await this.cipherService.decrypt(cipherDomain, activeUserId); if (!cipher.organizationId) { this.cipherIsAPartOfFreeOrg = false; diff --git a/apps/browser/src/vault/popup/components/vault-v2/autofill-vault-list-items/autofill-vault-list-items.component.ts b/apps/browser/src/vault/popup/components/vault-v2/autofill-vault-list-items/autofill-vault-list-items.component.ts index 72d51776f7b..b490d71df83 100644 --- a/apps/browser/src/vault/popup/components/vault-v2/autofill-vault-list-items/autofill-vault-list-items.component.ts +++ b/apps/browser/src/vault/popup/components/vault-v2/autofill-vault-list-items/autofill-vault-list-items.component.ts @@ -6,12 +6,7 @@ import { combineLatest, map, Observable } from "rxjs"; import { JslibModule } from "@bitwarden/angular/jslib.module"; import { VaultSettingsService } from "@bitwarden/common/vault/abstractions/vault-settings/vault-settings.service"; import { CipherType } from "@bitwarden/common/vault/enums"; -import { - IconButtonModule, - SectionComponent, - SectionHeaderComponent, - TypographyModule, -} from "@bitwarden/components"; +import { IconButtonModule, TypographyModule } from "@bitwarden/components"; import BrowserPopupUtils from "../../../../../platform/popup/browser-popup-utils"; import { VaultPopupAutofillService } from "../../../services/vault-popup-autofill.service"; @@ -20,14 +15,11 @@ import { PopupCipherView } from "../../../views/popup-cipher.view"; import { VaultListItemsContainerComponent } from "../vault-list-items-container/vault-list-items-container.component"; @Component({ - standalone: true, imports: [ CommonModule, - SectionComponent, TypographyModule, VaultListItemsContainerComponent, JslibModule, - SectionHeaderComponent, IconButtonModule, ], selector: "app-autofill-vault-list-items", diff --git a/apps/browser/src/vault/popup/components/vault-v2/blocked-injection-banner/blocked-injection-banner.component.ts b/apps/browser/src/vault/popup/components/vault-v2/blocked-injection-banner/blocked-injection-banner.component.ts index 3a17825f4fb..5824e8d97ea 100644 --- a/apps/browser/src/vault/popup/components/vault-v2/blocked-injection-banner/blocked-injection-banner.component.ts +++ b/apps/browser/src/vault/popup/components/vault-v2/blocked-injection-banner/blocked-injection-banner.component.ts @@ -16,7 +16,6 @@ import { VaultPopupAutofillService } from "../../../services/vault-popup-autofil const blockedURISettingsRoute = "/blocked-domains"; @Component({ - standalone: true, imports: [ BannerModule, CommonModule, diff --git a/apps/browser/src/vault/popup/components/vault-v2/intro-carousel/intro-carousel.component.ts b/apps/browser/src/vault/popup/components/vault-v2/intro-carousel/intro-carousel.component.ts index 96e1a70306e..527f0f246af 100644 --- a/apps/browser/src/vault/popup/components/vault-v2/intro-carousel/intro-carousel.component.ts +++ b/apps/browser/src/vault/popup/components/vault-v2/intro-carousel/intro-carousel.component.ts @@ -20,7 +20,6 @@ import { IntroCarouselService } from "../../../services/intro-carousel.service"; JslibModule, I18nPipe, ], - standalone: true, }) export class IntroCarouselComponent { protected securityHandshake = VaultIcons.SecurityHandshake; diff --git a/apps/browser/src/vault/popup/components/vault-v2/item-copy-action/item-copy-actions.component.html b/apps/browser/src/vault/popup/components/vault-v2/item-copy-action/item-copy-actions.component.html index bb3a7b12096..576f6b7def6 100644 --- a/apps/browser/src/vault/popup/components/vault-v2/item-copy-action/item-copy-actions.component.html +++ b/apps/browser/src/vault/popup/components/vault-v2/item-copy-action/item-copy-actions.component.html @@ -40,12 +40,9 @@ type="button" bitIconButton="bwi-clone" size="small" - [appA11yTitle]=" - 'copyFieldValue' | i18n: singleCopiableLogin.key : singleCopiableLogin.value - " - [appCopyClick]="singleCopiableLogin.value" - [valueLabel]="singleCopiableLogin.key" - showToast + [appA11yTitle]="singleCopiableLogin.key" + [appCopyField]="$any(singleCopiableLogin.field)" + [cipher]="cipher" > diff --git a/apps/browser/src/vault/popup/components/vault-v2/new-item-dropdown/new-item-dropdown-v2.component.spec.ts b/apps/browser/src/vault/popup/components/vault-v2/new-item-dropdown/new-item-dropdown-v2.component.spec.ts index a9b92274c9e..54c6ba2f788 100644 --- a/apps/browser/src/vault/popup/components/vault-v2/new-item-dropdown/new-item-dropdown-v2.component.spec.ts +++ b/apps/browser/src/vault/popup/components/vault-v2/new-item-dropdown/new-item-dropdown-v2.component.spec.ts @@ -94,8 +94,7 @@ describe("NewItemDropdownV2Component", () => { collectionId: "777-888-999", organizationId: "444-555-666", folderId: "222-333-444", - uri: "https://example.com", - name: "example.com", + prefillNameAndURIFromTab: "true", }); }); diff --git a/apps/browser/src/vault/popup/components/vault-v2/new-item-dropdown/new-item-dropdown-v2.component.ts b/apps/browser/src/vault/popup/components/vault-v2/new-item-dropdown/new-item-dropdown-v2.component.ts index bb452b89c7b..ef0b009025d 100644 --- a/apps/browser/src/vault/popup/components/vault-v2/new-item-dropdown/new-item-dropdown-v2.component.ts +++ b/apps/browser/src/vault/popup/components/vault-v2/new-item-dropdown/new-item-dropdown-v2.component.ts @@ -2,10 +2,9 @@ // @ts-strict-ignore import { CommonModule } from "@angular/common"; import { Component, Input, OnInit } from "@angular/core"; -import { Router, RouterLink } from "@angular/router"; +import { RouterLink } from "@angular/router"; import { JslibModule } from "@bitwarden/angular/jslib.module"; -import { Utils } from "@bitwarden/common/platform/misc/utils"; import { CollectionId, OrganizationId } from "@bitwarden/common/types/guid"; import { CipherType } from "@bitwarden/common/vault/enums"; import { ButtonModule, DialogService, MenuModule, NoItemsModule } from "@bitwarden/components"; @@ -24,7 +23,6 @@ export interface NewItemInitialValues { @Component({ selector: "app-new-item-dropdown", templateUrl: "new-item-dropdown-v2.component.html", - standalone: true, imports: [NoItemsModule, JslibModule, CommonModule, ButtonModule, RouterLink, MenuModule], }) export class NewItemDropdownV2Component implements OnInit { @@ -35,10 +33,8 @@ export class NewItemDropdownV2Component implements OnInit { */ @Input() initialValues: NewItemInitialValues; - constructor( - private router: Router, - private dialogService: DialogService, - ) {} + + constructor(private dialogService: DialogService) {} async ngOnInit() { this.tab = await BrowserApi.getTabFromCurrentWindow(); @@ -47,13 +43,12 @@ export class NewItemDropdownV2Component implements OnInit { buildQueryParams(type: CipherType): AddEditQueryParams { const poppedOut = BrowserPopupUtils.inPopout(window); - const loginDetails: { uri?: string; name?: string } = {}; + const loginDetails: { prefillNameAndURIFromTab?: string } = {}; // When a Login Cipher is created and the extension is not popped out, // pass along the uri and name if (!poppedOut && type === CipherType.Login && this.tab) { - loginDetails.uri = this.tab.url; - loginDetails.name = Utils.getHostname(this.tab.url); + loginDetails.prefillNameAndURIFromTab = "true"; } return { diff --git a/apps/browser/src/vault/popup/components/vault-v2/new-settings-callout/new-settings-callout.component.html b/apps/browser/src/vault/popup/components/vault-v2/new-settings-callout/new-settings-callout.component.html deleted file mode 100644 index 6cc60eed6d5..00000000000 --- a/apps/browser/src/vault/popup/components/vault-v2/new-settings-callout/new-settings-callout.component.html +++ /dev/null @@ -1,29 +0,0 @@ - - - -
- {{ "newCustomizationOptionsCalloutContent" | i18n }} - - {{ "newCustomizationOptionsCalloutLink" | i18n }} - -
-
-
diff --git a/apps/browser/src/vault/popup/components/vault-v2/new-settings-callout/new-settings-callout.component.ts b/apps/browser/src/vault/popup/components/vault-v2/new-settings-callout/new-settings-callout.component.ts deleted file mode 100644 index 713dc21c424..00000000000 --- a/apps/browser/src/vault/popup/components/vault-v2/new-settings-callout/new-settings-callout.component.ts +++ /dev/null @@ -1,81 +0,0 @@ -import { CommonModule } from "@angular/common"; -import { Component, OnDestroy, OnInit } from "@angular/core"; -import { Router } from "@angular/router"; -import { firstValueFrom } from "rxjs"; - -import { JslibModule } from "@bitwarden/angular/jslib.module"; -import { VaultProfileService } from "@bitwarden/angular/vault/services/vault-profile.service"; -import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; -import { getUserId } from "@bitwarden/common/auth/services/account.service"; -import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; -import { UserId } from "@bitwarden/common/types/guid"; -import { VaultSettingsService } from "@bitwarden/common/vault/abstractions/vault-settings/vault-settings.service"; -import { ButtonModule, PopoverModule } from "@bitwarden/components"; - -import { VaultPopupCopyButtonsService } from "../../../services/vault-popup-copy-buttons.service"; -import { VaultPageService } from "../vault-page.service"; - -@Component({ - selector: "new-settings-callout", - templateUrl: "new-settings-callout.component.html", - standalone: true, - imports: [PopoverModule, JslibModule, CommonModule, ButtonModule], - providers: [VaultPageService], -}) -export class NewSettingsCalloutComponent implements OnInit, OnDestroy { - protected showNewCustomizationSettingsCallout = false; - protected activeUserId: UserId | null = null; - - constructor( - private accountService: AccountService, - private vaultProfileService: VaultProfileService, - private vaultPageService: VaultPageService, - private router: Router, - private logService: LogService, - private copyButtonService: VaultPopupCopyButtonsService, - private vaultSettingsService: VaultSettingsService, - ) {} - - async ngOnInit() { - this.activeUserId = await firstValueFrom(this.accountService.activeAccount$.pipe(getUserId)); - - const showQuickCopyActions = await firstValueFrom(this.copyButtonService.showQuickCopyActions$); - const clickItemsToAutofillVaultView = await firstValueFrom( - this.vaultSettingsService.clickItemsToAutofillVaultView$, - ); - - let profileCreatedDate: Date; - - try { - profileCreatedDate = await this.vaultProfileService.getProfileCreationDate(this.activeUserId); - } catch (e) { - this.logService.error("Error getting profile creation date", e); - // Default to before the cutoff date to ensure the callout is shown - profileCreatedDate = new Date("2024-12-24"); - } - - const hasCalloutBeenDismissed = await firstValueFrom( - this.vaultPageService.isCalloutDismissed(this.activeUserId), - ); - - this.showNewCustomizationSettingsCallout = - !showQuickCopyActions && - !clickItemsToAutofillVaultView && - !hasCalloutBeenDismissed && - profileCreatedDate < new Date("2024-12-25"); - } - - async goToAppearance() { - await this.router.navigate(["/appearance"]); - } - - async dismissCallout() { - if (this.activeUserId) { - await this.vaultPageService.dismissCallout(this.activeUserId); - } - } - - async ngOnDestroy() { - await this.dismissCallout(); - } -} diff --git a/apps/browser/src/vault/popup/components/vault-v2/vault-generator-dialog/vault-generator-dialog.component.spec.ts b/apps/browser/src/vault/popup/components/vault-v2/vault-generator-dialog/vault-generator-dialog.component.spec.ts index dd5f55a66ee..b5d35e2005e 100644 --- a/apps/browser/src/vault/popup/components/vault-v2/vault-generator-dialog/vault-generator-dialog.component.spec.ts +++ b/apps/browser/src/vault/popup/components/vault-v2/vault-generator-dialog/vault-generator-dialog.component.spec.ts @@ -21,7 +21,6 @@ import { @Component({ selector: "vault-cipher-form-generator", template: "", - standalone: true, }) class MockCipherFormGenerator { @Input() type: "password" | "username" = "password"; diff --git a/apps/browser/src/vault/popup/components/vault-v2/vault-generator-dialog/vault-generator-dialog.component.ts b/apps/browser/src/vault/popup/components/vault-v2/vault-generator-dialog/vault-generator-dialog.component.ts index 137f2a9dac3..b0103aaacfb 100644 --- a/apps/browser/src/vault/popup/components/vault-v2/vault-generator-dialog/vault-generator-dialog.component.ts +++ b/apps/browser/src/vault/popup/components/vault-v2/vault-generator-dialog/vault-generator-dialog.component.ts @@ -5,6 +5,7 @@ import { CommonModule } from "@angular/common"; import { Component, Inject } from "@angular/core"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; +import { UnionOfValues } from "@bitwarden/common/vault/types/union-of-values"; import { DIALOG_DATA, DialogConfig, @@ -30,15 +31,16 @@ export interface GeneratorDialogResult { generatedValue?: string; } -export enum GeneratorDialogAction { - Selected = "selected", - Canceled = "canceled", -} +export const GeneratorDialogAction = { + Selected: "selected", + Canceled: "canceled", +} as const; + +type GeneratorDialogAction = UnionOfValues; @Component({ selector: "app-vault-generator-dialog", templateUrl: "./vault-generator-dialog.component.html", - standalone: true, imports: [ PopupPageComponent, PopupHeaderComponent, diff --git a/apps/browser/src/vault/popup/components/vault-v2/vault-header/vault-header-v2.component.ts b/apps/browser/src/vault/popup/components/vault-v2/vault-header/vault-header-v2.component.ts index bcea2e76190..f64b5e6b83d 100644 --- a/apps/browser/src/vault/popup/components/vault-v2/vault-header/vault-header-v2.component.ts +++ b/apps/browser/src/vault/popup/components/vault-v2/vault-header/vault-header-v2.component.ts @@ -20,7 +20,6 @@ import { VaultV2SearchComponent } from "../vault-search/vault-v2-search.componen @Component({ selector: "app-vault-header-v2", templateUrl: "vault-header-v2.component.html", - standalone: true, imports: [ VaultV2SearchComponent, VaultListFiltersComponent, diff --git a/apps/browser/src/vault/popup/components/vault-v2/vault-list-filters/vault-list-filters.component.html b/apps/browser/src/vault/popup/components/vault-v2/vault-list-filters/vault-list-filters.component.html index c61562f9f90..ba4cbf71251 100644 --- a/apps/browser/src/vault/popup/components/vault-v2/vault-list-filters/vault-list-filters.component.html +++ b/apps/browser/src/vault/popup/components/vault-v2/vault-list-filters/vault-list-filters.component.html @@ -20,7 +20,7 @@ *ngIf="collections.length" fullWidth formControlName="collection" - placeholderIcon="bwi-collection" + placeholderIcon="bwi-collection-shared" [placeholderText]="'collection' | i18n" [options]="collections" > diff --git a/apps/browser/src/vault/popup/components/vault-v2/vault-list-filters/vault-list-filters.component.ts b/apps/browser/src/vault/popup/components/vault-v2/vault-list-filters/vault-list-filters.component.ts index feccf92cec2..bc43a1d6a46 100644 --- a/apps/browser/src/vault/popup/components/vault-v2/vault-list-filters/vault-list-filters.component.ts +++ b/apps/browser/src/vault/popup/components/vault-v2/vault-list-filters/vault-list-filters.component.ts @@ -9,7 +9,6 @@ import { ChipSelectComponent } from "@bitwarden/components"; import { VaultPopupListFiltersService } from "../../../services/vault-popup-list-filters.service"; @Component({ - standalone: true, selector: "app-vault-list-filters", templateUrl: "./vault-list-filters.component.html", imports: [CommonModule, JslibModule, ChipSelectComponent, ReactiveFormsModule], diff --git a/apps/browser/src/vault/popup/components/vault-v2/vault-list-items-container/vault-list-items-container.component.ts b/apps/browser/src/vault/popup/components/vault-v2/vault-list-items-container/vault-list-items-container.component.ts index 6df1bdf8ae5..cef1ef8d2ff 100644 --- a/apps/browser/src/vault/popup/components/vault-v2/vault-list-items-container/vault-list-items-container.component.ts +++ b/apps/browser/src/vault/popup/components/vault-v2/vault-list-items-container/vault-list-items-container.component.ts @@ -74,11 +74,9 @@ import { ItemMoreOptionsComponent } from "../item-more-options/item-more-options ScrollingModule, DisclosureComponent, DisclosureTriggerForDirective, - DecryptionFailureDialogComponent, ], selector: "app-vault-list-items-container", templateUrl: "vault-list-items-container.component.html", - standalone: true, changeDetection: ChangeDetectionStrategy.OnPush, }) export class VaultListItemsContainerComponent implements OnInit, AfterViewInit { diff --git a/apps/browser/src/vault/popup/components/vault-v2/vault-page.service.ts b/apps/browser/src/vault/popup/components/vault-v2/vault-page.service.ts deleted file mode 100644 index a7c52ed4c51..00000000000 --- a/apps/browser/src/vault/popup/components/vault-v2/vault-page.service.ts +++ /dev/null @@ -1,35 +0,0 @@ -import { inject, Injectable } from "@angular/core"; -import { map, Observable } from "rxjs"; - -import { - BANNERS_DISMISSED_DISK, - StateProvider, - UserKeyDefinition, -} from "@bitwarden/common/platform/state"; -import { UserId } from "@bitwarden/common/types/guid"; - -export const NEW_CUSTOMIZATION_OPTIONS_CALLOUT_DISMISSED_KEY = new UserKeyDefinition( - BANNERS_DISMISSED_DISK, - "newCustomizationOptionsCalloutDismissed", - { - deserializer: (calloutDismissed) => calloutDismissed, - clearOn: [], // Do not clear dismissed callouts - }, -); - -@Injectable() -export class VaultPageService { - private stateProvider = inject(StateProvider); - - isCalloutDismissed(userId: UserId): Observable { - return this.stateProvider - .getUser(userId, NEW_CUSTOMIZATION_OPTIONS_CALLOUT_DISMISSED_KEY) - .state$.pipe(map((dismissed) => !!dismissed)); - } - - async dismissCallout(userId: UserId): Promise { - await this.stateProvider - .getUser(userId, NEW_CUSTOMIZATION_OPTIONS_CALLOUT_DISMISSED_KEY) - .update(() => true); - } -} diff --git a/apps/browser/src/vault/popup/components/vault-v2/vault-password-history-v2/vault-password-history-v2.component.ts b/apps/browser/src/vault/popup/components/vault-v2/vault-password-history-v2/vault-password-history-v2.component.ts index 5d315775b10..f2764df7ba7 100644 --- a/apps/browser/src/vault/popup/components/vault-v2/vault-password-history-v2/vault-password-history-v2.component.ts +++ b/apps/browser/src/vault/popup/components/vault-v2/vault-password-history-v2/vault-password-history-v2.component.ts @@ -19,7 +19,6 @@ import { PopupPageComponent } from "../../../../../platform/popup/layout/popup-p import { PopupRouterCacheService } from "../../../../../platform/popup/view-cache/popup-router-cache.service"; @Component({ - standalone: true, selector: "vault-password-history-v2", templateUrl: "vault-password-history-v2.component.html", imports: [ @@ -69,8 +68,6 @@ export class PasswordHistoryV2Component implements OnInit { const activeUserId = activeAccount.id as UserId; const cipher = await this.cipherService.get(cipherId, activeUserId); - this.cipher = await cipher.decrypt( - await this.cipherService.getKeyForCipherKeyDecryption(cipher, activeUserId), - ); + this.cipher = await this.cipherService.decrypt(cipher, activeUserId); } } diff --git a/apps/browser/src/vault/popup/components/vault-v2/vault-search/vault-v2-search.component.ts b/apps/browser/src/vault/popup/components/vault-v2/vault-search/vault-v2-search.component.ts index 32f5611f436..fe2baf463cf 100644 --- a/apps/browser/src/vault/popup/components/vault-v2/vault-search/vault-v2-search.component.ts +++ b/apps/browser/src/vault/popup/components/vault-v2/vault-search/vault-v2-search.component.ts @@ -1,8 +1,8 @@ import { CommonModule } from "@angular/common"; -import { Component } from "@angular/core"; +import { Component, NgZone } from "@angular/core"; import { takeUntilDestroyed } from "@angular/core/rxjs-interop"; import { FormsModule } from "@angular/forms"; -import { Subject, Subscription, debounceTime, filter } from "rxjs"; +import { Subject, Subscription, debounceTime, distinctUntilChanged, filter } from "rxjs"; import { JslibModule } from "@bitwarden/angular/jslib.module"; import { SearchModule } from "@bitwarden/components"; @@ -13,7 +13,6 @@ const SearchTextDebounceInterval = 200; @Component({ imports: [CommonModule, SearchModule, JslibModule, FormsModule], - standalone: true, selector: "app-vault-v2-search", templateUrl: "vault-v2-search.component.html", }) @@ -22,13 +21,16 @@ export class VaultV2SearchComponent { private searchText$ = new Subject(); - constructor(private vaultPopupItemsService: VaultPopupItemsService) { + constructor( + private vaultPopupItemsService: VaultPopupItemsService, + private ngZone: NgZone, + ) { this.subscribeToLatestSearchText(); this.subscribeToApplyFilter(); } onSearchTextChanged() { - this.vaultPopupItemsService.applyFilter(this.searchText); + this.searchText$.next(this.searchText); } subscribeToLatestSearchText(): Subscription { @@ -44,9 +46,13 @@ export class VaultV2SearchComponent { subscribeToApplyFilter(): Subscription { return this.searchText$ - .pipe(debounceTime(SearchTextDebounceInterval), takeUntilDestroyed()) + .pipe(debounceTime(SearchTextDebounceInterval), distinctUntilChanged(), takeUntilDestroyed()) .subscribe((data) => { - this.vaultPopupItemsService.applyFilter(data); + this.ngZone.runOutsideAngular(() => { + this.ngZone.run(() => { + this.vaultPopupItemsService.applyFilter(data); + }); + }); }); } } 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..ddd26b77425 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,33 @@ > - - - + + + + + + +
+ +
    +
  • {{ "hasItemsVaultNudgeBodyOne" | i18n }}
  • +
  • {{ "hasItemsVaultNudgeBodyTwo" | i18n }}
  • +
  • {{ "hasItemsVaultNudgeBodyThree" | i18n }}
  • +
+
+
+ + +
@@ -82,5 +105,4 @@ >
- 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..792f2b34f9f 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,41 @@ 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 { NudgesService, NudgeType } from "@bitwarden/angular/vault"; +import { SpotlightComponent } from "@bitwarden/angular/vault/components/spotlight/spotlight.component"; 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 { UnionOfValues } from "@bitwarden/common/vault/types/union-of-values"; +import { + ButtonModule, + DialogService, + Icons, + NoItemsModule, + TypographyModule, +} from "@bitwarden/components"; import { DecryptionFailureDialogComponent, VaultIcons } 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"; @@ -41,22 +52,21 @@ import { NewItemDropdownV2Component, NewItemInitialValues, } from "./new-item-dropdown/new-item-dropdown-v2.component"; -import { NewSettingsCalloutComponent } from "./new-settings-callout/new-settings-callout.component"; import { VaultHeaderV2Component } from "./vault-header/vault-header-v2.component"; -import { VaultPageService } from "./vault-page.service"; import { AutofillVaultListItemsComponent, VaultListItemsContainerComponent } from "."; -enum VaultState { - Empty, - NoResults, - DeactivatedOrg, -} +const VaultState = { + Empty: 0, + NoResults: 1, + DeactivatedOrg: 2, +} as const; + +type VaultState = UnionOfValues; @Component({ selector: "app-vault", templateUrl: "vault-v2.component.html", - standalone: true, imports: [ BlockedInjectionBanner, PopupPageComponent, @@ -73,15 +83,27 @@ enum VaultState { ScrollingModule, VaultHeaderV2Component, AtRiskPasswordCalloutComponent, - NewSettingsCalloutComponent, + SpotlightComponent, + RouterModule, + TypographyModule, ], - providers: [VaultPageService], }) export class VaultV2Component implements OnInit, AfterViewInit, OnDestroy { @ViewChild(CdkVirtualScrollableElement) virtualScrollElement?: CdkVirtualScrollableElement; + NudgeType = NudgeType; cipherType = CipherType; + private activeUserId$ = this.accountService.activeAccount$.pipe(getUserId); + showEmptyVaultSpotlight$: Observable = this.activeUserId$.pipe( + switchMap((userId) => + this.nudgesService.showNudgeSpotlight$(NudgeType.EmptyVaultNudge, userId), + ), + ); + showHasItemsVaultSpotlight$: Observable = this.activeUserId$.pipe( + switchMap((userId) => this.nudgesService.showNudgeSpotlight$(NudgeType.HasVaultItems, userId)), + ); + activeUserId: UserId | null = null; protected favoriteCiphers$ = this.vaultPopupItemsService.favoriteCiphers$; protected remainingCiphers$ = this.vaultPopupItemsService.remainingCiphers$; protected allFilters$ = this.vaultPopupListFiltersService.allFilters$; @@ -119,7 +141,6 @@ export class VaultV2Component implements OnInit, AfterViewInit, OnDestroy { protected noResultsIcon = Icons.NoResults; protected VaultStateEnum = VaultState; - protected showNewCustomizationSettingsCallout = false; constructor( private vaultPopupItemsService: VaultPopupItemsService, @@ -131,7 +152,8 @@ export class VaultV2Component implements OnInit, AfterViewInit, OnDestroy { private dialogService: DialogService, private vaultCopyButtonsService: VaultPopupCopyButtonsService, private introCarouselService: IntroCarouselService, - private configService: ConfigService, + private nudgesService: NudgesService, + private router: Router, ) { combineLatest([ this.vaultPopupItemsService.emptyVault$, @@ -169,16 +191,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 +214,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: NudgeType) { + await this.nudgesService.dismissNudge(type, this.activeUserId as UserId); + } + protected readonly FeatureFlag = FeatureFlag; } diff --git a/apps/browser/src/vault/popup/components/vault-v2/view-v2/view-v2.component.html b/apps/browser/src/vault/popup/components/vault-v2/view-v2/view-v2.component.html index b7ffeb89cc1..8c76db600ae 100644 --- a/apps/browser/src/vault/popup/components/vault-v2/view-v2/view-v2.component.html +++ b/apps/browser/src/vault/popup/components/vault-v2/view-v2/view-v2.component.html @@ -17,11 +17,7 @@ @@ -41,7 +47,7 @@ (click)="openAddEditFolderDialog()" data-testid="empty-new-folder-button" > - + {{ "newFolder" | i18n }} diff --git a/apps/browser/src/vault/popup/settings/folders-v2.component.spec.ts b/apps/browser/src/vault/popup/settings/folders-v2.component.spec.ts index 6689f5a6c6d..d1450667fa8 100644 --- a/apps/browser/src/vault/popup/settings/folders-v2.component.spec.ts +++ b/apps/browser/src/vault/popup/settings/folders-v2.component.spec.ts @@ -22,7 +22,6 @@ import { PopupHeaderComponent } from "../../../platform/popup/layout/popup-heade import { FoldersV2Component } from "./folders-v2.component"; @Component({ - standalone: true, selector: "popup-header", template: ``, }) @@ -32,7 +31,6 @@ class MockPopupHeaderComponent { } @Component({ - standalone: true, selector: "popup-footer", template: ``, }) diff --git a/apps/browser/src/vault/popup/settings/folders-v2.component.ts b/apps/browser/src/vault/popup/settings/folders-v2.component.ts index f71374e5305..2264415f4fa 100644 --- a/apps/browser/src/vault/popup/settings/folders-v2.component.ts +++ b/apps/browser/src/vault/popup/settings/folders-v2.component.ts @@ -22,7 +22,6 @@ import { PopupHeaderComponent } from "../../../platform/popup/layout/popup-heade import { PopupPageComponent } from "../../../platform/popup/layout/popup-page.component"; @Component({ - standalone: true, templateUrl: "./folders-v2.component.html", imports: [ CommonModule, diff --git a/apps/browser/src/tools/popup/settings/about-page/more-from-bitwarden-page-v2.component.html b/apps/browser/src/vault/popup/settings/more-from-bitwarden-page-v2.component.html similarity index 100% rename from apps/browser/src/tools/popup/settings/about-page/more-from-bitwarden-page-v2.component.html rename to apps/browser/src/vault/popup/settings/more-from-bitwarden-page-v2.component.html diff --git a/apps/browser/src/tools/popup/settings/about-page/more-from-bitwarden-page-v2.component.ts b/apps/browser/src/vault/popup/settings/more-from-bitwarden-page-v2.component.ts similarity index 90% rename from apps/browser/src/tools/popup/settings/about-page/more-from-bitwarden-page-v2.component.ts rename to apps/browser/src/vault/popup/settings/more-from-bitwarden-page-v2.component.ts index a3d1c553977..ec7a73a3bc3 100644 --- a/apps/browser/src/tools/popup/settings/about-page/more-from-bitwarden-page-v2.component.ts +++ b/apps/browser/src/vault/popup/settings/more-from-bitwarden-page-v2.component.ts @@ -11,15 +11,14 @@ import { BillingAccountProfileStateService } from "@bitwarden/common/billing/abs import { EnvironmentService } from "@bitwarden/common/platform/abstractions/environment.service"; import { DialogService, ItemModule } from "@bitwarden/components"; -import { FamiliesPolicyService } from "../../../../billing/services/families-policy.service"; -import { BrowserApi } from "../../../../platform/browser/browser-api"; -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"; +import { FamiliesPolicyService } from "../../../billing/services/families-policy.service"; +import { BrowserApi } from "../../../platform/browser/browser-api"; +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"; @Component({ templateUrl: "more-from-bitwarden-page-v2.component.html", - standalone: true, imports: [ CommonModule, JslibModule, diff --git a/apps/browser/src/vault/popup/settings/trash-list-items-container/trash-list-items-container.component.html b/apps/browser/src/vault/popup/settings/trash-list-items-container/trash-list-items-container.component.html index 70a2d317230..11ed2674178 100644 --- a/apps/browser/src/vault/popup/settings/trash-list-items-container/trash-list-items-container.component.html +++ b/apps/browser/src/vault/popup/settings/trash-list-items-container/trash-list-items-container.component.html @@ -31,14 +31,7 @@ >
{{ cipher.subTitle }} - + diff --git a/apps/browser/src/vault/popup/settings/vault-settings-v2.component.ts b/apps/browser/src/vault/popup/settings/vault-settings-v2.component.ts index c969f0436df..6f7940a2827 100644 --- a/apps/browser/src/vault/popup/settings/vault-settings-v2.component.ts +++ b/apps/browser/src/vault/popup/settings/vault-settings-v2.component.ts @@ -1,47 +1,64 @@ import { CommonModule } from "@angular/common"; -import { Component, OnInit } from "@angular/core"; +import { Component, OnDestroy, OnInit } from "@angular/core"; import { Router, RouterModule } from "@angular/router"; +import { firstValueFrom, switchMap } from "rxjs"; import { JslibModule } from "@bitwarden/angular/jslib.module"; +import { NudgesService, NudgeType } from "@bitwarden/angular/vault"; +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 { SyncService } from "@bitwarden/common/vault/abstractions/sync/sync.service.abstraction"; -import { ItemModule, ToastOptions, ToastService } from "@bitwarden/components"; +import { BadgeComponent, ItemModule, ToastOptions, ToastService } from "@bitwarden/components"; 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 { PopupFooterComponent } from "../../../platform/popup/layout/popup-footer.component"; import { PopupHeaderComponent } from "../../../platform/popup/layout/popup-header.component"; import { PopupPageComponent } from "../../../platform/popup/layout/popup-page.component"; @Component({ templateUrl: "vault-settings-v2.component.html", - standalone: true, imports: [ CommonModule, JslibModule, RouterModule, PopupPageComponent, - PopupFooterComponent, PopupHeaderComponent, PopOutComponent, ItemModule, + BadgeComponent, ], }) -export class VaultSettingsV2Component implements OnInit { +export class VaultSettingsV2Component implements OnInit, OnDestroy { lastSync = "--"; + protected emptyVaultImportBadge$ = this.accountService.activeAccount$.pipe( + getUserId, + switchMap((userId) => + this.nudgeService.showNudgeBadge$(NudgeType.VaultSettingsImportNudge, userId), + ), + ); + constructor( private router: Router, private syncService: SyncService, private toastService: ToastService, private i18nService: I18nService, + private nudgeService: NudgesService, + private accountService: AccountService, ) {} async ngOnInit() { await this.setLastSync(); } + async ngOnDestroy(): Promise { + // When a user navigates away from the page, dismiss the empty vault import nudge + const userId = await firstValueFrom(this.accountService.activeAccount$.pipe(getUserId)); + await this.nudgeService.dismissNudge(NudgeType.VaultSettingsImportNudge, userId); + } + async import() { await this.router.navigate(["/import"]); if (await BrowserApi.isPopupOpen()) { 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/store/locales/fa/copy.resx b/apps/browser/store/locales/fa/copy.resx index 69816ce3590..2d45e114719 100644 --- a/apps/browser/store/locales/fa/copy.resx +++ b/apps/browser/store/locales/fa/copy.resx @@ -118,58 +118,58 @@ System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - مدیریت رمز عبور Bitwarden + مدیر کلمه عبور Bitwarden - در خانه، محل کار، یا در حال حرکت، Bitwarden به سادگی تمامی رمزهای عبور، passkeyها، و اطلاعات حساس شما را امن نگاه می‌دارد. + در خانه، محل کار، یا در حال حرکت، Bitwarden به سادگی تمامی کلمات عبور، کلیدها، و اطلاعات حساس شما را امن نگاه می‌دارد. - 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. +همه باید ابزارهای ایمنی آنلاین را در اختیار داشته باشند +از Bitwarden به‌صورت رایگان استفاده کنید، بدون تبلیغات یا فروش داده‌ها. Bitwarden باور دارد که همه باید بتوانند در فضای آنلاین ایمن بمانند. طرح‌های پریمیوم، ویژگی‌های پیشرفته‌تری ارائه می‌دهند. -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. +توانمندسازی تیم‌ها با Bitwarden +طرح‌های ویژه تیم‌ها و سازمان‌ها شامل ویژگی‌های حرفه‌ای برای کسب‌وکار هستند، مانند: ادغام SSO، میزبانی شخصی، ادغام با دایرکتوری و فراهم‌سازی SCIM، سیاست‌های جهانی، دسترسی API، گزارش رویدادها و موارد دیگر. -Use Bitwarden to secure your workforce and share sensitive information with colleagues. +از Bitwarden برای ایمن‌سازی محیط کاری و اشتراک‌گذاری اطلاعات حساس با همکاران استفاده کنید. +دلایل بیشتر برای انتخاب Bitwarden: -More reasons to choose Bitwarden: +رمزگذاری سطح جهانی + کلمات عبور با رمزگذاری پیشرفته سرتاسری (AES-256 بیت، هش نمکی، و PBKDF2 SHA-256) محافظت می‌شوند تا داده‌های شما امن و خصوصی باقی بمانند. -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. +ممیزی‌های امنیتی توسط اشخاص ثالث + Bitwarden به‌طور منظم توسط شرکت‌های معتبر امنیتی مورد ارزیابی و تست نفوذ قرار می‌گیرد. این بررسی‌ها شامل بررسی کد منبع و تست‌های امنیتی روی IPها، سرورها و اپلیکیشن‌های وب Bitwarden است. -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. +احراز هویت دومرحله‌ای پیشرفته (2FA) + ورود امن با استفاده از احراز هویت‌کننده‌های شخص ثالث، کدهای ایمیلی، یا گواهی‌نامه‌های FIDO2 WebAuthn مانند کلید امنیتی سخت‌افزاری یا کلید عبور. Bitwarden Send -Transmit data directly to others while maintaining end-to-end encrypted security and limiting exposure. + ارسال مستقیم داده‌ها با رمزگذاری سرتاسری، با امکان محدودسازی دسترسی و مدت زمان مشاهده. -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. +مولد داخلی + تولید کلمات عبور بلند، پیچیده و منحصربه‌فرد و همچنین نام‌های کاربری برای هر وب‌سایتی که بازدید می‌کنید. امکان ادغام با ارائه‌دهندگان ایمیل مستعار برای حفظ بیشتر حریم خصوصی. -Global Translations -Bitwarden translations exist for more than 60 languages, translated by the global community though Crowdin. +ترجمه‌های جهانی + Bitwarden به بیش از ۶۰ زبان در دسترس است که توسط جامعه جهانی از طریق 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! +Bitwarden فقط کلمات عبور را ایمن نمی‌کند +راهکارهای مدیریت اعتبارات با رمزگذاری سرتاسری از Bitwarden به سازمان‌ها کمک می‌کند تا همه چیز را ایمن کنند، از جمله اسرار توسعه‌دهنده و تجربه‌های بدون کلمات عبور. +برای اطلاعات بیشتر درباره Bitwarden Secrets Manager و Bitwarden Passwordless.dev به وب‌سایت Bitwarden.com مراجعه کنید! - در خانه، محل کار، یا در حال حرکت، Bitwarden به سادگی تمامی رمزهای عبور، passkeyها، و اطلاعات حساس شما را امن نگاه می‌دارد. + در خانه، محل کار، یا در حال حرکت، Bitwarden به سادگی تمامی کلمات عبور، کلیدها، و اطلاعات حساس شما را امن نگاه می‌دارد. همگام‌سازی و دسترسی به گاوصندوق خود از دستگاه های مختلف diff --git a/apps/browser/store/locales/vi/copy.resx b/apps/browser/store/locales/vi/copy.resx index f1a65d0fed5..c262c9bffd7 100644 --- a/apps/browser/store/locales/vi/copy.resx +++ b/apps/browser/store/locales/vi/copy.resx @@ -121,7 +121,7 @@ Trình quản lý mật khẩu Bitwarden - At home, at work, or on the go, Bitwarden easily secures all your passwords, passkeys, and sensitive information. + Ở nhà, trên cơ quan, hay trên đường đi, Bitwarden có thể dễ dàng bảo về tất cả những mật khẩu, mã khoá, và thông tin cá nhân của bạn. Recognized as the best password manager by PCMag, WIRED, The Verge, CNET, G2, and more! diff --git a/apps/browser/tsconfig.json b/apps/browser/tsconfig.json index e24985f58af..a554120bd1e 100644 --- a/apps/browser/tsconfig.json +++ b/apps/browser/tsconfig.json @@ -1,55 +1,5 @@ { - "compilerOptions": { - "moduleResolution": "node", - "noImplicitAny": true, - "allowSyntheticDefaultImports": true, - "emitDecoratorMetadata": true, - "experimentalDecorators": true, - "module": "ES2020", - "target": "ES2016", - "allowJs": true, - "sourceMap": true, - "baseUrl": ".", - "lib": ["ES2021.String"], - "paths": { - "@bitwarden/admin-console/common": ["../../libs/admin-console/src/common"], - "@bitwarden/angular/*": ["../../libs/angular/src/*"], - "@bitwarden/auth/common": ["../../libs/auth/src/common"], - "@bitwarden/auth/angular": ["../../libs/auth/src/angular"], - "@bitwarden/billing": ["../../libs/billing/src"], - "@bitwarden/common/*": ["../../libs/common/src/*"], - "@bitwarden/components": ["../../libs/components/src"], - "@bitwarden/generator-components": ["../../libs/tools/generator/components/src"], - "@bitwarden/generator-core": ["../../libs/tools/generator/core/src"], - "@bitwarden/generator-history": ["../../libs/tools/generator/extensions/history/src"], - "@bitwarden/generator-legacy": ["../../libs/tools/generator/extensions/legacy/src"], - "@bitwarden/generator-navigation": ["../../libs/tools/generator/extensions/navigation/src"], - "@bitwarden/importer-core": ["../../libs/importer/src"], - "@bitwarden/importer-ui": ["../../libs/importer/src/components"], - "@bitwarden/key-management": ["../../libs/key-management/src"], - "@bitwarden/key-management-ui": ["../../libs/key-management-ui/src"], - "@bitwarden/platform": ["../../libs/platform/src"], - "@bitwarden/platform/*": ["../../libs/platform/src/*"], - "@bitwarden/send-ui": ["../../libs/tools/send/send-ui/src"], - "@bitwarden/tools-card": ["../../libs/tools/card/src"], - "@bitwarden/ui-common": ["../../libs/ui/common/src"], - "@bitwarden/ui-common/setup-jest": ["../../libs/ui/common/src/setup-jest"], - "@bitwarden/vault-export-core": [ - "../../libs/tools/export/vault-export/vault-export-core/src" - ], - "@bitwarden/vault-export-ui": ["../../libs/tools/export/vault-export/vault-export-ui/src"], - "@bitwarden/vault": ["../../libs/vault/src"] - }, - "plugins": [ - { - "name": "typescript-strict-plugin" - } - ], - "useDefineForClassFields": false - }, - "angularCompilerOptions": { - "strictTemplates": true - }, + "extends": "../../tsconfig.base", "include": [ "src", "../../libs/common/src/autofill/constants", diff --git a/apps/browser/tsconfig.spec.json b/apps/browser/tsconfig.spec.json index 79b5f5bc4b6..eedff91d23b 100644 --- a/apps/browser/tsconfig.spec.json +++ b/apps/browser/tsconfig.spec.json @@ -1,7 +1,9 @@ { "extends": "./tsconfig.json", - "files": ["./test.setup.ts"], "compilerOptions": { + "isolatedModules": true, + "emitDecoratorMetadata": false, "esModuleInterop": true - } + }, + "files": ["./test.setup.ts"] } diff --git a/apps/browser/webpack.config.js b/apps/browser/webpack.config.js index 4b66ed7d70a..e4f60aaf17a 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", @@ -307,8 +324,6 @@ if (manifestVersion == 2) { // Manifest V2 background pages can be run through the regular build pipeline. // Since it's a standard webpage. mainConfig.entry.background = "./src/platform/background.ts"; - mainConfig.entry["content/fido2-page-script-append-mv2"] = - "./src/autofill/fido2/content/fido2-page-script-append.mv2.ts"; mainConfig.entry["content/fido2-page-script-delay-append-mv2"] = "./src/autofill/fido2/content/fido2-page-script-delay-append.mv2.ts"; @@ -357,6 +372,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 +401,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 @@ -[![Github Workflow build on master](https://github.com/bitwarden/clients/actions/workflows/build-cli.yml/badge.svg?branch=master)](https://github.com/bitwarden/clients/actions/workflows/build-cli.yml?query=branch:master) +[![Github Workflow build on main](https://github.com/bitwarden/clients/actions/workflows/build-cli.yml/badge.svg?branch=main)](https://github.com/bitwarden/clients/actions/workflows/build-cli.yml?query=branch:main) [![Join the chat at https://gitter.im/bitwarden/Lobby](https://badges.gitter.im/bitwarden/Lobby.svg)](https://gitter.im/bitwarden/Lobby) # Bitwarden Command-line Interface diff --git a/apps/cli/jest.config.js b/apps/cli/jest.config.js index e0a5b9ec9cc..c96395944b5 100644 --- a/apps/cli/jest.config.js +++ b/apps/cli/jest.config.js @@ -1,6 +1,6 @@ const { pathsToModuleNameMapper } = require("ts-jest"); -const { compilerOptions } = require("./tsconfig"); +const { compilerOptions } = require("../../tsconfig.base"); const sharedConfig = require("../../libs/shared/jest.config.ts"); @@ -13,8 +13,14 @@ module.exports = { moduleNameMapper: { "@bitwarden/common/platform/services/sdk/default-sdk-client-factory": "/../../libs/common/spec/jest-sdk-client-factory", - ...pathsToModuleNameMapper(compilerOptions?.paths || {}, { - prefix: "/", - }), + ...pathsToModuleNameMapper( + { + "@bitwarden/common/spec": ["libs/common/spec"], + ...(compilerOptions?.paths || {}), + }, + { + prefix: "/../../", + }, + ), }, }; diff --git a/apps/cli/package.json b/apps/cli/package.json index 04fe4290d31..befbed2ef20 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.5.0", "keywords": [ "bitwarden", "password", @@ -64,31 +64,33 @@ ] }, "dependencies": { - "@koa/multer": "3.0.2", + "@koa/multer": "3.1.0", "@koa/router": "13.1.0", "argon2": "0.41.1", "big-integer": "1.6.52", "browser-hrtime": "1.1.8", "chalk": "4.1.2", "commander": "11.1.0", + "core-js": "3.42.0", "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", "koa-json": "2.0.2", "lowdb": "1.0.0", "lunr": "2.3.9", - "multer": "1.4.5-lts.1", + "multer": "1.4.5-lts.2", "node-fetch": "2.6.12", "node-forge": "1.3.1", - "open": "8.4.2", - "papaparse": "5.5.2", + "open": "10.1.2", + "papaparse": "5.5.3", "proper-lockfile": "4.1.2", "rxjs": "7.8.1", - "tldts": "6.1.77", + "semver": "7.7.2", + "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/admin-console/commands/share.command.ts b/apps/cli/src/admin-console/commands/share.command.ts index 6d9e6c8b6c0..540bc2659c9 100644 --- a/apps/cli/src/admin-console/commands/share.command.ts +++ b/apps/cli/src/admin-console/commands/share.command.ts @@ -59,15 +59,11 @@ export class ShareCommand { return Response.badRequest("This item already belongs to an organization."); } - const cipherView = await cipher.decrypt( - await this.cipherService.getKeyForCipherKeyDecryption(cipher, activeUserId), - ); + const cipherView = await this.cipherService.decrypt(cipher, activeUserId); try { await this.cipherService.shareWithServer(cipherView, organizationId, req, activeUserId); const updatedCipher = await this.cipherService.get(cipher.id, activeUserId); - const decCipher = await updatedCipher.decrypt( - await this.cipherService.getKeyForCipherKeyDecryption(updatedCipher, activeUserId), - ); + const decCipher = await this.cipherService.decrypt(updatedCipher, activeUserId); const res = new CipherResponse(decCipher); return Response.success(res); } catch (e) { diff --git a/apps/cli/src/auth/commands/login.command.ts b/apps/cli/src/auth/commands/login.command.ts index 8d66a566038..03af0085e67 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,17 +29,18 @@ 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"; -import { ErrorResponse } from "@bitwarden/common/models/response/error.response"; +import { MasterPasswordServiceAbstraction } from "@bitwarden/common/key-management/master-password/abstractions/master-password.service.abstraction"; import { EnvironmentService } from "@bitwarden/common/platform/abstractions/environment.service"; +import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; import { Utils } from "@bitwarden/common/platform/misc/utils"; import { 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"; @@ -77,6 +78,8 @@ export class LoginCommand { protected logoutCallback: () => Promise, protected kdfConfigService: KdfConfigService, protected ssoUrlService: SsoUrlService, + protected i18nService: I18nService, + protected masterPasswordService: MasterPasswordServiceAbstraction, ) {} async run(email: string, password: string, options: OptionValues) { @@ -105,6 +108,8 @@ export class LoginCommand { return Response.badRequest("client_secret is required."); } } else if (options.sso != null && this.canInteract) { + // If the optional Org SSO Identifier isn't provided, the option value is `true`. + const orgSsoIdentifier = options.sso === true ? null : options.sso; const passwordOptions: any = { type: "password", length: 64, @@ -118,7 +123,7 @@ export class LoginCommand { const codeVerifierHash = await this.cryptoFunctionService.hash(ssoCodeVerifier, "sha256"); const codeChallenge = Utils.fromBufferToUrlB64(codeVerifierHash); try { - const ssoParams = await this.openSsoPrompt(codeChallenge, state); + const ssoParams = await this.openSsoPrompt(codeChallenge, state, orgSsoIdentifier); ssoCode = ssoParams.ssoCode; orgIdentifier = ssoParams.orgIdentifier; } catch { @@ -220,24 +225,11 @@ export class LoginCommand { ); } else { response = await this.loginStrategyService.logIn( - new PasswordLoginCredentials(email, password, null, twoFactor), + new PasswordLoginCredentials(email, password, twoFactor), ); } if (response.requiresEncryptionKeyMigration) { - return Response.error( - "Encryption key migration required. Please login through the web vault to update your encryption key.", - ); - } - if (response.captchaSiteKey) { - const credentials = new PasswordLoginCredentials(email, password); - const handledResponse = await this.handleCaptchaRequired(twoFactor, credentials); - - // Error Response - if (handledResponse instanceof Response) { - return handledResponse; - } else { - response = handledResponse; - } + return Response.error(this.i18nService.t("legacyEncryptionUnsupported")); } if (response.requiresTwoFactor) { const twoFactorProviders = await this.twoFactorService.getSupportedProviders(null); @@ -281,11 +273,7 @@ export class LoginCommand { } } - if ( - twoFactorToken == null && - Object.keys(response.twoFactorProviders).length > 1 && - selectedProvider.type === TwoFactorProviderType.Email - ) { + if (twoFactorToken == null && selectedProvider.type === TwoFactorProviderType.Email) { const emailReq = new TwoFactorEmailRequest(); emailReq.email = await this.loginStrategyService.getEmail(); emailReq.masterPasswordHash = await this.loginStrategyService.getMasterPasswordHash(); @@ -310,7 +298,6 @@ export class LoginCommand { response = await this.loginStrategyService.logInTwoFactor( new TokenTwoFactorRequest(selectedProvider.type, twoFactorToken), - null, ); } @@ -334,18 +321,6 @@ export class LoginCommand { response = await this.loginStrategyService.logInNewDeviceVerification(newDeviceToken); } - if (response.captchaSiteKey) { - const twoFactorRequest = new TokenTwoFactorRequest(selectedProvider.type, twoFactorToken); - const handledResponse = await this.handleCaptchaRequired(twoFactorRequest); - - // Error Response - if (handledResponse instanceof Response) { - return handledResponse; - } else { - response = handledResponse; - } - } - if (response.requiresTwoFactor) { return Response.error("Login failed."); } @@ -361,15 +336,15 @@ export class LoginCommand { await this.syncService.fullSync(true); // Handle updating passwords if NOT using an API Key for authentication - if ( - response.forcePasswordReset != ForceSetPasswordReason.None && - clientId == null && - clientSecret == null - ) { - if (response.forcePasswordReset === ForceSetPasswordReason.AdminForcePasswordReset) { - return await this.updateTempPassword(); - } else if (response.forcePasswordReset === ForceSetPasswordReason.WeakMasterPassword) { - return await this.updateWeakPassword(password); + if (clientId == null && clientSecret == null) { + const forceSetPasswordReason = await firstValueFrom( + this.masterPasswordService.forceSetPasswordReason$(response.userId), + ); + + if (forceSetPasswordReason === ForceSetPasswordReason.AdminForcePasswordReset) { + return await this.updateTempPassword(response.userId); + } else if (forceSetPasswordReason === ForceSetPasswordReason.WeakMasterPassword) { + return await this.updateWeakPassword(response.userId, password); } } @@ -431,7 +406,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 +423,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 +445,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 +462,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 +487,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 +518,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 +536,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 +549,7 @@ export class LoginCommand { ) ) { return this.collectNewMasterPasswordDetails( + userId, prompt, "Your new master password does not meet the policy requirements.\n", ); @@ -589,6 +567,7 @@ export class LoginCommand { // Re-type Validation if (masterPassword !== masterPasswordRetype) { return this.collectNewMasterPasswordDetails( + userId, prompt, "Master password confirmation does not match.\n", ); @@ -601,7 +580,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( @@ -623,48 +602,6 @@ export class LoginCommand { return { newPasswordHash, newUserKey: newUserKey, hint: masterPasswordHint }; } - private async handleCaptchaRequired( - twoFactorRequest: TokenTwoFactorRequest, - credentials: PasswordLoginCredentials = null, - ): Promise { - const badCaptcha = Response.badRequest( - "Your authentication request has been flagged and will require user interaction to proceed.\n" + - "Please use your API key to validate this request and ensure BW_CLIENTSECRET is correct, if set.\n" + - "(https://bitwarden.com/help/cli-auth-challenges)", - ); - - try { - const captchaClientSecret = await this.apiClientSecret(true); - if (Utils.isNullOrWhitespace(captchaClientSecret)) { - return badCaptcha; - } - - let authResultResponse: AuthResult = null; - if (credentials != null) { - credentials.captchaToken = captchaClientSecret; - credentials.twoFactor = twoFactorRequest; - authResultResponse = await this.loginStrategyService.logIn(credentials); - } else { - authResultResponse = await this.loginStrategyService.logInTwoFactor( - twoFactorRequest, - captchaClientSecret, - ); - } - - return authResultResponse; - } catch (e) { - if ( - e instanceof ErrorResponse || - (e.constructor.name === ErrorResponse.name && - (e as ErrorResponse).message.includes("Captcha is invalid")) - ) { - return badCaptcha; - } else { - return Response.error(e); - } - } - } - private async apiClientId(): Promise { let clientId: string = null; @@ -725,6 +662,7 @@ export class LoginCommand { private async openSsoPrompt( codeChallenge: string, state: string, + orgSsoIdentifier: string, ): Promise<{ ssoCode: string; orgIdentifier: string }> { const env = await firstValueFrom(this.environmentService.environment$); @@ -773,6 +711,8 @@ export class LoginCommand { this.ssoRedirectUri, state, codeChallenge, + null, + orgSsoIdentifier, ); this.platformUtilsService.launchUri(webAppSsoUrl); }); diff --git a/apps/cli/src/auth/commands/unlock.command.ts b/apps/cli/src/auth/commands/unlock.command.ts index 6d524759dd6..812a89ed889 100644 --- a/apps/cli/src/auth/commands/unlock.command.ts +++ b/apps/cli/src/auth/commands/unlock.command.ts @@ -14,12 +14,12 @@ import { EnvironmentService } from "@bitwarden/common/platform/abstractions/envi import { Utils } from "@bitwarden/common/platform/misc/utils"; import { ConsoleLogService } from "@bitwarden/common/platform/services/console-log.service"; import { MasterKey } from "@bitwarden/common/types/key"; -import { SyncService } from "@bitwarden/common/vault/abstractions/sync/sync.service.abstraction"; import { KeyService } from "@bitwarden/key-management"; -import { ConvertToKeyConnectorCommand } from "../../commands/convert-to-key-connector.command"; +import { ConvertToKeyConnectorCommand } from "../../key-management/convert-to-key-connector.command"; import { Response } from "../../models/response"; import { MessageResponse } from "../../models/response/message.response"; +import { I18nService } from "../../platform/services/i18n.service"; import { CliUtils } from "../../utils"; export class UnlockCommand { @@ -32,9 +32,9 @@ export class UnlockCommand { private logService: ConsoleLogService, private keyConnectorService: KeyConnectorService, private environmentService: EnvironmentService, - private syncService: SyncService, private organizationApiService: OrganizationApiServiceAbstraction, private logout: () => Promise, + private i18nService: I18nService, ) {} async run(password: string, cmdOptions: Record) { @@ -73,14 +73,14 @@ export class UnlockCommand { const userKey = await this.masterPasswordService.decryptUserKeyWithMasterKey(masterKey, userId); await this.keyService.setUserKey(userKey, userId); - if (await this.keyConnectorService.getConvertAccountRequired()) { + if (await firstValueFrom(this.keyConnectorService.convertAccountRequired$)) { const convertToKeyConnectorCommand = new ConvertToKeyConnectorCommand( userId, this.keyConnectorService, this.environmentService, - this.syncService, this.organizationApiService, this.logout, + this.i18nService, ); const convertResponse = await convertToKeyConnectorCommand.run(); if (!convertResponse.success) { diff --git a/apps/cli/src/base-program.ts b/apps/cli/src/base-program.ts index 14c930b804a..5719f78c1b9 100644 --- a/apps/cli/src/base-program.ts +++ b/apps/cli/src/base-program.ts @@ -179,9 +179,9 @@ export abstract class BaseProgram { this.serviceContainer.logService, this.serviceContainer.keyConnectorService, this.serviceContainer.environmentService, - this.serviceContainer.syncService, this.serviceContainer.organizationApiService, this.serviceContainer.logout, + this.serviceContainer.i18nService, ); const response = await command.run(null, null); if (!response.success) { diff --git a/apps/cli/src/commands/download.command.ts b/apps/cli/src/commands/download.command.ts index 01ef675d2a8..2c75617ca5b 100644 --- a/apps/cli/src/commands/download.command.ts +++ b/apps/cli/src/commands/download.command.ts @@ -2,8 +2,6 @@ // @ts-strict-ignore import { ApiService } from "@bitwarden/common/abstractions/api.service"; import { EncryptService } from "@bitwarden/common/key-management/crypto/abstractions/encrypt.service"; -import { EncArrayBuffer } from "@bitwarden/common/platform/models/domain/enc-array-buffer"; -import { SymmetricCryptoKey } from "@bitwarden/common/platform/models/domain/symmetric-crypto-key"; import { Response } from "../models/response"; import { FileResponse } from "../models/response/file.response"; @@ -25,15 +23,15 @@ export abstract class DownloadCommand { /** * Fetches an attachment via the url, decrypts it's content and saves it to a file * @param url - url used to retrieve the attachment - * @param key - SymmetricCryptoKey to decrypt the file contents * @param fileName - filename used when written to disk + * @param decrypt - Function used to decrypt the response * @param output - If output is empty or `--raw` was passed to the initial command the content is output onto stdout * @returns Promise */ protected async saveAttachmentToFile( url: string, - key: SymmetricCryptoKey, fileName: string, + decrypt: (resp: globalThis.Response) => Promise, output?: string, ) { const response = await this.apiService.nativeFetch( @@ -46,8 +44,7 @@ export abstract class DownloadCommand { } try { - const encBuf = await EncArrayBuffer.fromResponse(response); - const decBuf = await this.encryptService.decryptToBytes(encBuf, key); + const decBuf = await decrypt(response); if (process.env.BW_SERVE === "true") { const res = new FileResponse(Buffer.from(decBuf), fileName); return Response.success(res); diff --git a/apps/cli/src/commands/edit.command.ts b/apps/cli/src/commands/edit.command.ts index 2d4a854135d..677139d5451 100644 --- a/apps/cli/src/commands/edit.command.ts +++ b/apps/cli/src/commands/edit.command.ts @@ -90,9 +90,7 @@ export class EditCommand { return Response.notFound(); } - let cipherView = await cipher.decrypt( - await this.cipherService.getKeyForCipherKeyDecryption(cipher, activeUserId), - ); + let cipherView = await this.cipherService.decrypt(cipher, activeUserId); if (cipherView.isDeleted) { return Response.badRequest("You may not edit a deleted item. Use the restore command first."); } @@ -100,9 +98,7 @@ export class EditCommand { const encCipher = await this.cipherService.encrypt(cipherView, activeUserId); try { const updatedCipher = await this.cipherService.updateWithServer(encCipher); - const decCipher = await updatedCipher.decrypt( - await this.cipherService.getKeyForCipherKeyDecryption(updatedCipher, activeUserId), - ); + const decCipher = await this.cipherService.decrypt(updatedCipher, activeUserId); const res = new CipherResponse(decCipher); return Response.success(res); } catch (e) { @@ -132,12 +128,7 @@ export class EditCommand { cipher, activeUserId, ); - const decCipher = await updatedCipher.decrypt( - await this.cipherService.getKeyForCipherKeyDecryption( - updatedCipher, - await firstValueFrom(this.accountService.activeAccount$.pipe(getUserId)), - ), - ); + const decCipher = await this.cipherService.decrypt(updatedCipher, activeUserId); const res = new CipherResponse(decCipher); return Response.success(res); } catch (e) { @@ -204,7 +195,7 @@ export class EditCommand { (u) => new SelectionReadOnlyRequest(u.id, u.readOnly, u.hidePasswords, u.manage), ); const request = new CollectionRequest(); - request.name = (await this.encryptService.encrypt(req.name, orgKey)).encryptedString; + request.name = (await this.encryptService.encryptString(req.name, orgKey)).encryptedString; request.externalId = req.externalId; request.groups = groups; request.users = users; diff --git a/apps/cli/src/commands/get.command.ts b/apps/cli/src/commands/get.command.ts index 1bdbd051585..8554f8e2ae1 100644 --- a/apps/cli/src/commands/get.command.ts +++ b/apps/cli/src/commands/get.command.ts @@ -27,7 +27,7 @@ import { ErrorResponse } from "@bitwarden/common/models/response/error.response" import { Utils } from "@bitwarden/common/platform/misc/utils"; import { EncString } from "@bitwarden/common/platform/models/domain/enc-string"; import { SendType } from "@bitwarden/common/tools/send/enums/send-type"; -import { OrganizationId } from "@bitwarden/common/types/guid"; +import { CipherId, OrganizationId } 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 { TotpService } from "@bitwarden/common/vault/abstractions/totp.service"; @@ -116,9 +116,7 @@ export class GetCommand extends DownloadCommand { if (Utils.isGuid(id)) { const cipher = await this.cipherService.get(id, activeUserId); if (cipher != null) { - decCipher = await cipher.decrypt( - await this.cipherService.getKeyForCipherKeyDecryption(cipher, activeUserId), - ); + decCipher = await this.cipherService.decrypt(cipher, activeUserId); } } else if (id.trim() !== "") { let ciphers = await this.cipherService.getAllDecrypted(activeUserId); @@ -347,12 +345,11 @@ export class GetCommand extends DownloadCommand { return Response.multipleResults(attachments.map((a) => a.id)); } - const account = await firstValueFrom(this.accountService.activeAccount$); + const activeUserId = await firstValueFrom(this.accountService.activeAccount$.pipe(getUserId)); const canAccessPremium = await firstValueFrom( - this.accountProfileService.hasPremiumFromAnySource$(account.id), + this.accountProfileService.hasPremiumFromAnySource$(activeUserId), ); if (!canAccessPremium) { - const activeUserId = await firstValueFrom(this.accountService.activeAccount$.pipe(getUserId)); const originalCipher = await this.cipherService.get(cipher.id, activeUserId); if (originalCipher == null || originalCipher.organizationId == null) { return Response.error("Premium status is required to use this feature."); @@ -376,11 +373,20 @@ export class GetCommand extends DownloadCommand { } } - const key = - attachments[0].key != null - ? attachments[0].key - : await this.keyService.getOrgKey(cipher.organizationId); - return await this.saveAttachmentToFile(url, key, attachments[0].fileName, options.output); + const decryptBufferFn = (resp: globalThis.Response) => + this.cipherService.getDecryptedAttachmentBuffer( + cipher.id as CipherId, + attachments[0], + resp, + activeUserId, + ); + + return await this.saveAttachmentToFile( + url, + attachments[0].fileName, + decryptBufferFn, + options.output, + ); } private async getFolder(id: string) { @@ -455,10 +461,9 @@ export class GetCommand extends DownloadCommand { const response = await this.apiService.getCollectionAccessDetails(options.organizationId, id); const decCollection = new CollectionView(response); - decCollection.name = await this.encryptService.decryptToUtf8( + decCollection.name = await this.encryptService.decryptString( new EncString(response.name), orgKey, - `orgkey-${options.organizationId}`, ); const groups = response.groups == null diff --git a/apps/cli/src/commands/restore.command.ts b/apps/cli/src/commands/restore.command.ts index 7064feb0136..0b30193ffd4 100644 --- a/apps/cli/src/commands/restore.command.ts +++ b/apps/cli/src/commands/restore.command.ts @@ -1,9 +1,7 @@ -import { combineLatest, firstValueFrom, map } from "rxjs"; +import { firstValueFrom } from "rxjs"; 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 { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service"; import { CipherAuthorizationService } from "@bitwarden/common/vault/services/cipher-authorization.service"; @@ -13,7 +11,6 @@ export class RestoreCommand { constructor( private cipherService: CipherService, private accountService: AccountService, - private configService: ConfigService, private cipherAuthorizationService: CipherAuthorizationService, ) {} @@ -42,17 +39,7 @@ export class RestoreCommand { } const canRestore = await firstValueFrom( - combineLatest([ - this.configService.getFeatureFlag$(FeatureFlag.LimitItemDeletion), - this.cipherAuthorizationService.canRestoreCipher$(cipher), - ]).pipe( - map(([enabled, canRestore]) => { - if (enabled && !canRestore) { - return false; - } - return true; - }), - ), + this.cipherAuthorizationService.canRestoreCipher$(cipher), ); if (!canRestore) { diff --git a/apps/cli/src/key-management/convert-to-key-connector.command.spec.ts b/apps/cli/src/key-management/convert-to-key-connector.command.spec.ts new file mode 100644 index 00000000000..6b0d4e6f685 --- /dev/null +++ b/apps/cli/src/key-management/convert-to-key-connector.command.spec.ts @@ -0,0 +1,206 @@ +import { createPromptModule } from "inquirer"; +import { mock } from "jest-mock-extended"; +import { of } 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 { KeyConnectorService } from "@bitwarden/common/key-management/key-connector/abstractions/key-connector.service"; +import { + Environment, + EnvironmentService, + Region, + Urls, +} from "@bitwarden/common/platform/abstractions/environment.service"; +import { UserId } from "@bitwarden/common/types/guid"; + +import { Response } from "../models/response"; +import { MessageResponse } from "../models/response/message.response"; +import { I18nService } from "../platform/services/i18n.service"; + +import { ConvertToKeyConnectorCommand } from "./convert-to-key-connector.command"; + +jest.mock("inquirer", () => { + return { + createPromptModule: jest.fn(() => jest.fn(() => Promise.resolve({ convert: "" }))), + }; +}); + +describe("ConvertToKeyConnectorCommand", () => { + let command: ConvertToKeyConnectorCommand; + + const userId = "test-user-id" as UserId; + const organization = { + id: "test-organization-id", + name: "Test Organization", + keyConnectorUrl: "https://keyconnector.example.com", + } as Organization; + + const keyConnectorService = mock(); + const environmentService = mock(); + const organizationApiService = mock(); + const logout = jest.fn(); + const i18nService = mock(); + + beforeEach(async () => { + command = new ConvertToKeyConnectorCommand( + userId, + keyConnectorService, + environmentService, + organizationApiService, + logout, + i18nService, + ); + + i18nService.t.mockImplementation((key: string) => { + switch (key) { + case "removeMasterPasswordForOrganizationUserKeyConnector": + return "A master password is no longer required for members of the following organization. Please confirm the domain below with your organization administrator. Organization name: Test Organization. Key Connector domain: https://keyconnector.example.com"; + case "removeMasterPasswordAndUnlock": + return "Remove master password and unlock"; + case "leaveOrganizationAndUnlock": + return "Leave organization and unlock"; + case "logOut": + return "Log out"; + case "youHaveBeenLoggedOut": + return "You have been logged out."; + case "organizationUsingKeyConnectorOptInLoggedOut": + return "An organization you are a member of is using Key Connector. In order to access the vault, you must opt-in to Key Connector now via the web vault. You have been logged out."; + default: + return ""; + } + }); + }); + + describe("run", () => { + it("should logout and return error response if no interaction available", async () => { + process.env.BW_NOINTERACTION = "true"; + + const response = await command.run(); + + expect(response).not.toBeNull(); + expect(response.success).toEqual(false); + expect(response).toEqual( + Response.error( + new MessageResponse( + "An organization you are a member of is using Key Connector. In order to access the vault, you must opt-in to Key Connector now via the web vault. You have been logged out.", + null, + ), + ), + ); + expect(logout).toHaveBeenCalled(); + }); + + it("should logout and return error response if interaction answer is exit", async () => { + process.env.BW_NOINTERACTION = "false"; + keyConnectorService.getManagingOrganization.mockResolvedValue(organization); + + (createPromptModule as jest.Mock).mockImplementation(() => + jest.fn((prompt) => { + assertPrompt(prompt); + return Promise.resolve({ convert: "exit" }); + }), + ); + + const response = await command.run(); + + expect(response).not.toBeNull(); + expect(response.success).toEqual(false); + expect(response).toEqual(Response.error("You have been logged out.")); + expect(logout).toHaveBeenCalled(); + }); + + it("should key connector migrate user and return success response if answer is remove", async () => { + process.env.BW_NOINTERACTION = "false"; + keyConnectorService.getManagingOrganization.mockResolvedValue(organization); + environmentService.environment$ = of({ + getUrls: () => + ({ + keyConnector: "old-key-connector-url", + }) as Urls, + } as Environment); + + (createPromptModule as jest.Mock).mockImplementation(() => + jest.fn((prompt) => { + assertPrompt(prompt); + return Promise.resolve({ convert: "remove" }); + }), + ); + + const response = await command.run(); + + expect(response).not.toBeNull(); + expect(response.success).toEqual(true); + expect(keyConnectorService.migrateUser).toHaveBeenCalledWith( + organization.keyConnectorUrl, + userId, + ); + expect(environmentService.setEnvironment).toHaveBeenCalledWith(Region.SelfHosted, { + keyConnector: organization.keyConnectorUrl, + } as Urls); + }); + + it("should logout and throw error if key connector migrate user fails", async () => { + process.env.BW_NOINTERACTION = "false"; + keyConnectorService.getManagingOrganization.mockResolvedValue(organization); + + (createPromptModule as jest.Mock).mockImplementation(() => + jest.fn((prompt) => { + assertPrompt(prompt); + return Promise.resolve({ convert: "remove" }); + }), + ); + + keyConnectorService.migrateUser.mockRejectedValue(new Error("Migration failed")); + + await expect(command.run()).rejects.toThrow("Migration failed"); + expect(logout).toHaveBeenCalled(); + }); + + it("should leave organization and return success response if answer is leave", async () => { + process.env.BW_NOINTERACTION = "false"; + keyConnectorService.getManagingOrganization.mockResolvedValue(organization); + + (createPromptModule as jest.Mock).mockImplementation(() => + jest.fn((prompt) => { + assertPrompt(prompt); + return Promise.resolve({ convert: "leave" }); + }), + ); + + const response = await command.run(); + + expect(response).not.toBeNull(); + expect(response.success).toEqual(true); + expect(organizationApiService.leave).toHaveBeenCalledWith(organization.id); + }); + + function assertPrompt(prompt: unknown) { + expect(typeof prompt).toEqual("object"); + expect(prompt).toHaveProperty("type"); + expect(prompt).toHaveProperty("name"); + expect(prompt).toHaveProperty("message"); + expect(prompt).toHaveProperty("choices"); + const promptObj = prompt as Record; + expect(promptObj["type"]).toEqual("list"); + expect(promptObj["name"]).toEqual("convert"); + expect(promptObj["message"]).toEqual( + `A master password is no longer required for members of the following organization. Please confirm the domain below with your organization administrator. Organization name: ${organization.name}. Key Connector domain: ${organization.keyConnectorUrl}`, + ); + expect(promptObj["choices"]).toBeInstanceOf(Array); + const choices = promptObj["choices"] as Array>; + expect(choices).toHaveLength(3); + expect(choices[0]).toEqual({ + name: "Remove master password and unlock", + value: "remove", + }); + expect(choices[1]).toEqual({ + name: "Leave organization and unlock", + value: "leave", + }); + expect(choices[2]).toEqual({ + name: "Log out", + value: "exit", + }); + } + }); +}); diff --git a/apps/cli/src/commands/convert-to-key-connector.command.ts b/apps/cli/src/key-management/convert-to-key-connector.command.ts similarity index 68% rename from apps/cli/src/commands/convert-to-key-connector.command.ts rename to apps/cli/src/key-management/convert-to-key-connector.command.ts index cdebfd7b55a..ff1de744d74 100644 --- a/apps/cli/src/commands/convert-to-key-connector.command.ts +++ b/apps/cli/src/key-management/convert-to-key-connector.command.ts @@ -1,5 +1,3 @@ -// FIXME: Update this file to be type safe and remove this and next line -// @ts-strict-ignore import * as inquirer from "inquirer"; import { firstValueFrom } from "rxjs"; @@ -10,19 +8,19 @@ import { Region, } from "@bitwarden/common/platform/abstractions/environment.service"; import { UserId } from "@bitwarden/common/types/guid"; -import { SyncService } from "@bitwarden/common/vault/abstractions/sync/sync.service.abstraction"; import { Response } from "../models/response"; import { MessageResponse } from "../models/response/message.response"; +import { I18nService } from "../platform/services/i18n.service"; export class ConvertToKeyConnectorCommand { constructor( private readonly userId: UserId, private keyConnectorService: KeyConnectorService, private environmentService: EnvironmentService, - private syncService: SyncService, private organizationApiService: OrganizationApiServiceAbstraction, private logout: () => Promise, + private i18nService: I18nService, ) {} async run(): Promise { @@ -32,32 +30,33 @@ export class ConvertToKeyConnectorCommand { await this.logout(); return Response.error( new MessageResponse( - "An organization you are a member of is using Key Connector. " + - "In order to access the vault, you must opt-in to Key Connector now via the web vault. You have been logged out.", + this.i18nService.t("organizationUsingKeyConnectorOptInLoggedOut"), null, ), ); } - const organization = await this.keyConnectorService.getManagingOrganization(); + const organization = await this.keyConnectorService.getManagingOrganization(this.userId); const answer: inquirer.Answers = await inquirer.createPromptModule({ output: process.stderr })({ type: "list", name: "convert", - message: - organization.name + - " is using a self-hosted key server. A master password is no longer required to log in for members of this organization. ", + message: this.i18nService.t( + "removeMasterPasswordForOrganizationUserKeyConnector", + organization.name, + organization.keyConnectorUrl, + ), choices: [ { - name: "Remove master password and unlock", + name: this.i18nService.t("removeMasterPasswordAndUnlock"), value: "remove", }, { - name: "Leave organization and unlock", + name: this.i18nService.t("leaveOrganizationAndUnlock"), value: "leave", }, { - name: "Log out", + name: this.i18nService.t("logOut"), value: "exit", }, ], @@ -65,15 +64,12 @@ export class ConvertToKeyConnectorCommand { if (answer.convert === "remove") { try { - await this.keyConnectorService.migrateUser(); + await this.keyConnectorService.migrateUser(organization.keyConnectorUrl, this.userId); } catch (e) { await this.logout(); throw e; } - await this.keyConnectorService.removeConvertAccountRequired(); - await this.keyConnectorService.setUsesKeyConnector(true, this.userId); - // Update environment URL - required for api key login const env = await firstValueFrom(this.environmentService.environment$); const urls = env.getUrls(); @@ -83,11 +79,10 @@ export class ConvertToKeyConnectorCommand { return Response.success(); } else if (answer.convert === "leave") { await this.organizationApiService.leave(organization.id); - await this.keyConnectorService.removeConvertAccountRequired(); return Response.success(); } else { await this.logout(); - return Response.error("You have been logged out."); + return Response.error(this.i18nService.t("youHaveBeenLoggedOut")); } } } diff --git a/apps/cli/src/locales/en/messages.json b/apps/cli/src/locales/en/messages.json index a9c7af91524..815939c0c95 100644 --- a/apps/cli/src/locales/en/messages.json +++ b/apps/cli/src/locales/en/messages.json @@ -184,5 +184,36 @@ "example": "JustTrust.us" } } + }, + "legacyEncryptionUnsupported": { + "message": "Legacy encryption is no longer supported. Please contact support to recover your account." + }, + "organizationUsingKeyConnectorOptInLoggedOut": { + "message": "An organization you are a member of is using Key Connector. In order to access the vault, you must opt-in to Key Connector now via the web vault. You have been logged out." + }, + "removeMasterPasswordForOrganizationUserKeyConnector": { + "message": "A master password is no longer required for members of the following organization. Please confirm the domain below with your organization administrator. Organization name: $ORGANIZATION$. Key Connector domain: $KEYCONNECTORDOMAIN$", + "placeholders": { + "organization": { + "content": "$1", + "example": "My Org Name" + }, + "keyConnectorDomain": { + "content": "$2", + "example": "Key Connector domain" + } + } + }, + "removeMasterPasswordAndUnlock": { + "message": "Remove master password and unlock" + }, + "leaveOrganizationAndUnlock": { + "message": "Leave organization and unlock" + }, + "logOut": { + "message": "Log out" + }, + "youHaveBeenLoggedOut": { + "message": "You have been logged out." } } diff --git a/apps/cli/src/models/response/message.response.ts b/apps/cli/src/models/response/message.response.ts index dcbe3b92216..889d0982f83 100644 --- a/apps/cli/src/models/response/message.response.ts +++ b/apps/cli/src/models/response/message.response.ts @@ -5,11 +5,11 @@ import { BaseResponse } from "./base.response"; export class MessageResponse implements BaseResponse { object: string; title: string; - message: string; + message: string | null; raw: string; noColor = false; - constructor(title: string, message: string) { + constructor(title: string, message: string | null) { this.object = "message"; this.title = title; this.message = message; diff --git a/apps/cli/src/oss-serve-configurator.ts b/apps/cli/src/oss-serve-configurator.ts index 7028e65a690..1b11b467388 100644 --- a/apps/cli/src/oss-serve-configurator.ts +++ b/apps/cli/src/oss-serve-configurator.ts @@ -127,7 +127,6 @@ export class OssServeConfigurator { this.restoreCommand = new RestoreCommand( this.serviceContainer.cipherService, this.serviceContainer.accountService, - this.serviceContainer.configService, this.serviceContainer.cipherAuthorizationService, ); this.shareCommand = new ShareCommand( @@ -144,9 +143,9 @@ export class OssServeConfigurator { this.serviceContainer.logService, this.serviceContainer.keyConnectorService, this.serviceContainer.environmentService, - this.serviceContainer.syncService, this.serviceContainer.organizationApiService, async () => await this.serviceContainer.logout(), + this.serviceContainer.i18nService, ); this.sendCreateCommand = new SendCreateCommand( diff --git a/apps/cli/src/platform/services/cli-platform-utils.service.ts b/apps/cli/src/platform/services/cli-platform-utils.service.ts index 87b1a79435c..4e00b58607b 100644 --- a/apps/cli/src/platform/services/cli-platform-utils.service.ts +++ b/apps/cli/src/platform/services/cli-platform-utils.service.ts @@ -2,12 +2,11 @@ // @ts-strict-ignore import * as child_process from "child_process"; +import open from "open"; + import { ClientType, DeviceType } from "@bitwarden/common/enums"; import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; -// eslint-disable-next-line -const open = require("open"); - export class CliPlatformUtilsService implements PlatformUtilsService { clientType: ClientType; @@ -84,7 +83,8 @@ export class CliPlatformUtilsService implements PlatformUtilsService { if (process.platform === "linux") { child_process.spawnSync("xdg-open", [uri]); } else { - open(uri); + // eslint-disable-next-line no-console + open(uri).catch(console.error); } } diff --git a/apps/cli/src/platform/services/node-api.service.ts b/apps/cli/src/platform/services/node-api.service.ts index 8c7629fb3d9..d695272364b 100644 --- a/apps/cli/src/platform/services/node-api.service.ts +++ b/apps/cli/src/platform/services/node-api.service.ts @@ -39,6 +39,7 @@ export class NodeApiService extends ApiService { logService, logoutCallback, vaultTimeoutSettingsService, + { createRequest: (url, request) => new Request(url, request) }, customUserAgent, ); } diff --git a/apps/cli/src/platform/services/node-env-secure-storage.service.ts b/apps/cli/src/platform/services/node-env-secure-storage.service.ts index 5e31995606f..64865340000 100644 --- a/apps/cli/src/platform/services/node-env-secure-storage.service.ts +++ b/apps/cli/src/platform/services/node-env-secure-storage.service.ts @@ -61,7 +61,7 @@ export class NodeEnvSecureStorageService implements AbstractStorageService { if (sessionKey == null) { throw new Error("No session key available."); } - const encValue = await this.encryptService.encryptToBytes( + const encValue = await this.encryptService.encryptFileData( Utils.fromB64ToArray(plainValue), sessionKey, ); @@ -80,7 +80,7 @@ export class NodeEnvSecureStorageService implements AbstractStorageService { } const encBuf = EncArrayBuffer.fromB64(encValue); - const decValue = await this.encryptService.decryptToBytes(encBuf, sessionKey); + const decValue = await this.encryptService.decryptFileData(encBuf, sessionKey); if (decValue == null) { this.logService.info("Failed to decrypt."); return null; diff --git a/apps/cli/src/program.ts b/apps/cli/src/program.ts index c6b79c7dff2..468901282b4 100644 --- a/apps/cli/src/program.ts +++ b/apps/cli/src/program.ts @@ -118,7 +118,10 @@ export class Program extends BaseProgram { .description("Log into a user account.") .option("--method ", "Two-step login method.") .option("--code ", "Two-step login code.") - .option("--sso", "Log in with Single-Sign On.") + .option( + "--sso [identifier]", + "Log in with Single-Sign On with optional organization identifier.", + ) .option("--apikey", "Log in with an Api Key.") .option("--passwordenv ", "Environment variable storing your password") .option( @@ -172,6 +175,8 @@ export class Program extends BaseProgram { async () => await this.serviceContainer.logout(), this.serviceContainer.kdfConfigService, this.serviceContainer.ssoUrlService, + this.serviceContainer.i18nService, + this.serviceContainer.masterPasswordService, ); const response = await command.run(email, password, options); this.processResponse(response, true); @@ -277,9 +282,9 @@ export class Program extends BaseProgram { this.serviceContainer.logService, this.serviceContainer.keyConnectorService, this.serviceContainer.environmentService, - this.serviceContainer.syncService, this.serviceContainer.organizationApiService, async () => await this.serviceContainer.logout(), + this.serviceContainer.i18nService, ); const response = await command.run(password, cmd); this.processResponse(response); diff --git a/apps/cli/src/service-container/service-container.ts b/apps/cli/src/service-container/service-container.ts index fe2f506f229..05437e3e3d3 100644 --- a/apps/cli/src/service-container/service-container.ts +++ b/apps/cli/src/service-container/service-container.ts @@ -139,12 +139,14 @@ import { SendApiService } from "@bitwarden/common/tools/send/services/send-api.s import { SendStateProvider } from "@bitwarden/common/tools/send/services/send-state.provider"; import { SendService } from "@bitwarden/common/tools/send/services/send.service"; import { UserId } from "@bitwarden/common/types/guid"; +import { CipherEncryptionService } from "@bitwarden/common/vault/abstractions/cipher-encryption.service"; import { InternalFolderService } from "@bitwarden/common/vault/abstractions/folder/folder.service.abstraction"; import { CipherAuthorizationService, DefaultCipherAuthorizationService, } from "@bitwarden/common/vault/services/cipher-authorization.service"; import { CipherService } from "@bitwarden/common/vault/services/cipher.service"; +import { DefaultCipherEncryptionService } from "@bitwarden/common/vault/services/default-cipher-encryption.service"; import { CipherFileUploadService } from "@bitwarden/common/vault/services/file-upload/cipher-file-upload.service"; import { FolderApiService } from "@bitwarden/common/vault/services/folder/folder-api.service"; import { FolderService } from "@bitwarden/common/vault/services/folder/folder.service"; @@ -284,6 +286,7 @@ export class ServiceContainer { ssoUrlService: SsoUrlService; masterPasswordApiService: MasterPasswordApiServiceAbstraction; bulkEncryptService: FallbackBulkEncryptService; + cipherEncryptionService: CipherEncryptionService; constructor() { let p = null; @@ -679,6 +682,11 @@ export class ServiceContainer { this.accountService, ); + this.cipherEncryptionService = new DefaultCipherEncryptionService( + this.sdkService, + this.logService, + ); + this.cipherService = new CipherService( this.keyService, this.domainSettingsService, @@ -694,6 +702,7 @@ export class ServiceContainer { this.stateProvider, this.accountService, this.logService, + this.cipherEncryptionService, ); this.folderService = new FolderService( @@ -706,8 +715,8 @@ export class ServiceContainer { this.folderApiService = new FolderApiService(this.folderService, this.apiService); - const lockedCallback = async (userId?: string) => - await this.keyService.clearStoredUserKey(KeySuffixOptions.Auto); + const lockedCallback = async (userId: UserId) => + await this.keyService.clearStoredUserKey(KeySuffixOptions.Auto, userId); this.userVerificationApiService = new UserVerificationApiService(this.apiService); @@ -852,7 +861,6 @@ export class ServiceContainer { this.collectionService, this.organizationService, this.accountService, - this.configService, ); this.masterPasswordApiService = new MasterPasswordApiService(this.apiService, this.logService); @@ -865,7 +873,7 @@ export class ServiceContainer { const userId = await firstValueFrom(getUserId(this.accountService.activeAccount$)); await Promise.all([ this.eventUploadService.uploadEvents(userId as UserId), - this.keyService.clearKeys(), + this.keyService.clearKeys(userId), this.cipherService.clear(userId), this.folderService.clear(userId), this.collectionService.clear(userId), diff --git a/apps/cli/src/tools/send/commands/receive.command.ts b/apps/cli/src/tools/send/commands/receive.command.ts index c67b4213d97..a412f7c1667 100644 --- a/apps/cli/src/tools/send/commands/receive.command.ts +++ b/apps/cli/src/tools/send/commands/receive.command.ts @@ -11,6 +11,7 @@ import { ErrorResponse } from "@bitwarden/common/models/response/error.response" import { EnvironmentService } from "@bitwarden/common/platform/abstractions/environment.service"; import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; import { Utils } from "@bitwarden/common/platform/misc/utils"; +import { EncArrayBuffer } from "@bitwarden/common/platform/models/domain/enc-array-buffer"; import { SymmetricCryptoKey } from "@bitwarden/common/platform/models/domain/symmetric-crypto-key"; import { SendType } from "@bitwarden/common/tools/send/enums/send-type"; import { SendAccess } from "@bitwarden/common/tools/send/models/domain/send-access"; @@ -98,10 +99,16 @@ export class SendReceiveCommand extends DownloadCommand { this.sendAccessRequest, apiUrl, ); + + const decryptBufferFn = async (resp: globalThis.Response) => { + const encBuf = await EncArrayBuffer.fromResponse(resp); + return this.encryptService.decryptFileData(encBuf, this.decKey); + }; + return await this.saveAttachmentToFile( downloadData.url, - this.decKey, response?.file?.fileName, + decryptBufferFn, options.output, ); } diff --git a/apps/cli/src/utils.ts b/apps/cli/src/utils.ts index fadede9c71b..e321adbfd5e 100644 --- a/apps/cli/src/utils.ts +++ b/apps/cli/src/utils.ts @@ -161,7 +161,6 @@ export class CliUtils { process.stdin.setEncoding("utf8"); process.stdin.on("readable", () => { - // eslint-disable-next-line while (true) { const chunk = process.stdin.read(); if (chunk == null) { diff --git a/apps/cli/src/vault.program.ts b/apps/cli/src/vault.program.ts index ce6ac2af94e..4393075810d 100644 --- a/apps/cli/src/vault.program.ts +++ b/apps/cli/src/vault.program.ts @@ -350,7 +350,6 @@ export class VaultProgram extends BaseProgram { const command = new RestoreCommand( this.serviceContainer.cipherService, this.serviceContainer.accountService, - this.serviceContainer.configService, this.serviceContainer.cipherAuthorizationService, ); const response = await command.run(object, id); diff --git a/apps/cli/src/vault/create.command.ts b/apps/cli/src/vault/create.command.ts index 713471356c9..b1536e23748 100644 --- a/apps/cli/src/vault/create.command.ts +++ b/apps/cli/src/vault/create.command.ts @@ -93,9 +93,7 @@ export class CreateCommand { const cipher = await this.cipherService.encrypt(CipherExport.toView(req), activeUserId); try { const newCipher = await this.cipherService.createWithServer(cipher); - const decCipher = await newCipher.decrypt( - await this.cipherService.getKeyForCipherKeyDecryption(newCipher, activeUserId), - ); + const decCipher = await this.cipherService.decrypt(newCipher, activeUserId); const res = new CipherResponse(decCipher); return Response.success(res); } catch (e) { @@ -162,9 +160,7 @@ export class CreateCommand { new Uint8Array(fileBuf).buffer, activeUserId, ); - const decCipher = await updatedCipher.decrypt( - await this.cipherService.getKeyForCipherKeyDecryption(updatedCipher, activeUserId), - ); + const decCipher = await this.cipherService.decrypt(updatedCipher, activeUserId); return Response.success(new CipherResponse(decCipher)); } catch (e) { return Response.error(e); @@ -227,7 +223,7 @@ export class CreateCommand { (u) => new SelectionReadOnlyRequest(u.id, u.readOnly, u.hidePasswords, u.manage), ); const request = new CollectionRequest(); - request.name = (await this.encryptService.encrypt(req.name, orgKey)).encryptedString; + request.name = (await this.encryptService.encryptString(req.name, orgKey)).encryptedString; request.externalId = req.externalId; request.groups = groups; request.users = users; diff --git a/apps/cli/src/vault/delete.command.ts b/apps/cli/src/vault/delete.command.ts index 9e648cd9bb0..d1b0b093cf8 100644 --- a/apps/cli/src/vault/delete.command.ts +++ b/apps/cli/src/vault/delete.command.ts @@ -106,6 +106,7 @@ export class DeleteCommand { cipher.id, attachments[0].id, activeUserId, + false, ); return Response.success(); } catch (e) { 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/cli/tsconfig.json b/apps/cli/tsconfig.json index 9d6e3066b29..3bcd098ca19 100644 --- a/apps/cli/tsconfig.json +++ b/apps/cli/tsconfig.json @@ -1,38 +1,4 @@ { - "compilerOptions": { - "pretty": true, - "moduleResolution": "node", - "target": "ES2016", - "module": "ES2020", - "noImplicitAny": true, - "allowSyntheticDefaultImports": true, - "emitDecoratorMetadata": true, - "experimentalDecorators": true, - "allowJs": true, - "sourceMap": true, - "baseUrl": ".", - "paths": { - "@bitwarden/common/spec": ["../../libs/common/spec"], - "@bitwarden/admin-console/common": ["../../libs/admin-console/src/common"], - "@bitwarden/auth/common": ["../../libs/auth/src/common"], - "@bitwarden/auth/angular": ["../../libs/auth/src/angular"], - "@bitwarden/common/*": ["../../libs/common/src/*"], - "@bitwarden/importer-core": ["../../libs/importer/src"], - "@bitwarden/generator-core": ["../../libs/tools/generator/core/src"], - "@bitwarden/generator-legacy": ["../../libs/tools/generator/extensions/legacy/src"], - "@bitwarden/generator-history": ["../../libs/tools/generator/extensions/history/src"], - "@bitwarden/generator-navigation": ["../../libs/tools/generator/extensions/navigation/src"], - "@bitwarden/vault-export-core": [ - "../../libs/tools/export/vault-export/vault-export-core/src" - ], - "@bitwarden/key-management": ["../../libs/key-management/src"], - "@bitwarden/node/*": ["../../libs/node/src/*"] - }, - "plugins": [ - { - "name": "typescript-strict-plugin" - } - ] - }, + "extends": "../../tsconfig.base", "include": ["src", "src/**/*.spec.ts"] } 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 @@ -[![Github Workflow build on master](https://github.com/bitwarden/clients/actions/workflows/build-desktop.yml/badge.svg?branch=master)](https://github.com/bitwarden/clients/actions/workflows/build-desktop.yml?query=branch:master) +[![Github Workflow build on main](https://github.com/bitwarden/clients/actions/workflows/build-desktop.yml/badge.svg?branch=main)](https://github.com/bitwarden/clients/actions/workflows/build-desktop.yml?query=branch:main) [![Crowdin](https://d322cqt584bo4o.cloudfront.net/bitwarden-desktop/localized.svg)](https://crowdin.com/project/bitwarden-desktop) [![Join the chat at https://gitter.im/bitwarden/Lobby](https://badges.gitter.im/bitwarden/Lobby.svg)](https://gitter.im/bitwarden/Lobby) diff --git a/apps/desktop/desktop_native/Cargo.lock b/apps/desktop/desktop_native/Cargo.lock index 04bc4887ff7..05663ea7e0b 100644 --- a/apps/desktop/desktop_native/Cargo.lock +++ b/apps/desktop/desktop_native/Cargo.lock @@ -120,9 +120,9 @@ checksum = "c1fd03a028ef38ba2276dce7e33fcd6369c158a1bca17946c4b1b701891c1ff7" [[package]] name = "arboard" -version = "3.4.1" +version = "3.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df099ccb16cd014ff054ac1bf392c67feeef57164b05c42f037cd40f5d4357f4" +checksum = "c1df21f715862ede32a0c525ce2ca4d52626bb0007f8c18b87a384503ac33e70" dependencies = [ "clipboard-win", "log", @@ -130,6 +130,7 @@ dependencies = [ "objc2-app-kit", "objc2-foundation", "parking_lot", + "percent-encoding", "wl-clipboard-rs", "x11rb", ] @@ -147,6 +148,23 @@ dependencies = [ "zeroize", ] +[[package]] +name = "ashpd" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6cbdf310d77fd3aaee6ea2093db7011dc2d35d2eb3481e5607f1f8d942ed99df" +dependencies = [ + "enumflags2", + "futures-channel", + "futures-util", + "rand 0.9.1", + "serde", + "serde_repr", + "tokio", + "url", + "zbus 5.6.0", +] + [[package]] name = "askama" version = "0.12.1" @@ -214,14 +232,15 @@ dependencies = [ [[package]] name = "async-executor" -version = "1.13.1" +version = "1.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "30ca9a001c1e8ba5149f91a74362376cc6bc5b919d92d988668657bd570bdcec" +checksum = "bb812ffb58524bdd10860d7d974e2f01cc0950c2438a74ee5ec2e2280c6c4ffa" dependencies = [ "async-task", "concurrent-queue", "fastrand", "futures-lite", + "pin-project-lite", "slab", ] @@ -249,7 +268,7 @@ dependencies = [ "futures-lite", "parking", "polling", - "rustix", + "rustix 0.38.44", "slab", "tracing", "windows-sys 0.59.0", @@ -266,17 +285,6 @@ dependencies = [ "pin-project-lite", ] -[[package]] -name = "async-net" -version = "2.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b948000fad4873c1c9339d60f2623323a0cfd3816e5181033c6a5cb68b2accf7" -dependencies = [ - "async-io", - "blocking", - "futures-lite", -] - [[package]] name = "async-process" version = "2.3.0" @@ -292,7 +300,7 @@ dependencies = [ "cfg-if", "event-listener", "futures-lite", - "rustix", + "rustix 0.38.44", "tracing", ] @@ -319,7 +327,7 @@ dependencies = [ "cfg-if", "futures-core", "futures-io", - "rustix", + "rustix 0.38.44", "signal-hook-registry", "slab", "windows-sys 0.59.0", @@ -333,9 +341,9 @@ checksum = "8b75356056920673b02621b35afd0f7dda9306d03c79a30f5c56c44cf256e3de" [[package]] name = "async-trait" -version = "0.1.86" +version = "0.1.88" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "644dd749086bf3771a2fbc5f256fdb982d53f011c7d5d560304eafeecebce79d" +checksum = "e539d3fca749fcee5236ab05e93a52867dd549cc157c8cb7f99595f3cedffdb5" dependencies = [ "proc-macro2", "quote", @@ -356,9 +364,9 @@ checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" [[package]] name = "backtrace" -version = "0.3.74" +version = "0.3.75" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8d82cb332cdfaed17ae235a638438ac4d4839913cc2af585c3c6746e8f8bee1a" +checksum = "6806a6321ec58106fea15becdad98371e28d92ccbc7c8f1b3b6dd724fe8f1002" dependencies = [ "addr2line", "cfg-if", @@ -377,15 +385,15 @@ checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" [[package]] name = "base64ct" -version = "1.6.0" +version = "1.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8c3c1a368f70d6cf7302d78f8f7093da241fb8e8807c05cc9e51a125895a6d5b" +checksum = "89e25b6adfb930f02d1981565a6e5d9c547ac15a96606256d3b59040e5cd4ca3" [[package]] name = "basic-toml" -version = "0.1.9" +version = "0.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "823388e228f614e9558c6804262db37960ec8821856535f5c3f59913140558f8" +checksum = "ba62675e8242a4c4e806d12f11d136e626e6c8361d6b829310732241652a178a" dependencies = [ "serde", ] @@ -410,31 +418,11 @@ 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" +version = "2.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f68f53c83ab957f72c32642f3868eec03eb974d1fb82e453128456482613d36" +checksum = "5c8214115b7bf84099f1309324e63141d4c5d7cc26862f97a0a857dbefe165bd" [[package]] name = "bitwarden-russh" @@ -479,15 +467,6 @@ dependencies = [ "generic-array", ] -[[package]] -name = "block2" -version = "0.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2c132eebf10f5cad5289222520a4a058514204aed6d791f1cf4fe8088b82d15f" -dependencies = [ - "objc2", -] - [[package]] name = "blocking" version = "1.6.1" @@ -519,9 +498,9 @@ checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" [[package]] name = "bytes" -version = "1.10.0" +version = "1.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f61dac84819c6588b558454b194026eb1f09c293b9036ae9b159e74e73ab6cf9" +checksum = "d71b6127be86fdcfddb610f7182ac57211d4b18a3e9c82eb2d17662f2227ad6a" [[package]] name = "camino" @@ -573,27 +552,12 @@ 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" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" -[[package]] -name = "cfg_aliases" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fd16c4719339c4530435d38e511904438d07cce7950afa3718a84ac36c10e89e" - [[package]] name = "cfg_aliases" version = "0.2.1" @@ -622,22 +586,11 @@ 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" +version = "4.5.38" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "027bb0d98429ae334a8698531da7077bdf906419543a35a55c2cb1b66437d767" +checksum = "ed93b9805f8ba930df42c2590f05453d5ec36cbb85d018868a5b24d31f6ac000" dependencies = [ "clap_builder", "clap_derive", @@ -645,9 +598,9 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.5.31" +version = "4.5.38" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5589e0cba072e0f3d23791efac0fd8627b49c829c196a492e88168e6a669d863" +checksum = "379026ff283facf611b0ea629334361c4211d1b12ee01024eec1591133b04120" dependencies = [ "anstream", "anstyle", @@ -657,9 +610,9 @@ dependencies = [ [[package]] name = "clap_derive" -version = "4.5.28" +version = "4.5.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bf4ced95c6f4a675af3da73304b9ac4ed991640c36374e4b46795c49e17cf1ed" +checksum = "09176aae279615badda0765c0c0b3f6ed53f4709118af73cf4655d85d1530cd7" dependencies = [ "heck", "proc-macro2", @@ -684,10 +637,11 @@ dependencies = [ [[package]] name = "codespan-reporting" -version = "0.11.1" +version = "0.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3538270d33cc669650c4b093848450d380def10c331d38c768e34cac80576e6e" +checksum = "fe6d2e5af09e8c8ad56c969f2157a3d4238cebc7c55f0a517728c38f7b200f81" dependencies = [ + "serde", "termcolor", "unicode-width", ] @@ -747,25 +701,6 @@ dependencies = [ "libc", ] -[[package]] -name = "crossbeam-deque" -version = "0.8.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9dd111b7b7f7d55b72c0a6ae361660ee5853c9af73f70c3c2ef6858b950e2e51" -dependencies = [ - "crossbeam-epoch", - "crossbeam-utils", -] - -[[package]] -name = "crossbeam-epoch" -version = "0.9.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e" -dependencies = [ - "crossbeam-utils", -] - [[package]] name = "crossbeam-utils" version = "0.8.21" @@ -779,7 +714,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" dependencies = [ "generic-array", - "rand_core", + "rand_core 0.6.4", "typenum", ] @@ -830,9 +765,9 @@ dependencies = [ [[package]] name = "cxx" -version = "1.0.141" +version = "1.0.158" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8bc580dceb395cae0efdde0a88f034cfd8a276897e40c693a7b87bed17971d33" +checksum = "a71ea7f29c73f7ffa64c50b83c9fe4d3a6d4be89a86b009eb80d5a6d3429d741" dependencies = [ "cc", "cxxbridge-cmd", @@ -844,9 +779,9 @@ dependencies = [ [[package]] name = "cxx-build" -version = "1.0.141" +version = "1.0.158" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49d8c1baedad72a7efda12ad8d7ad687b3e7221dfb304a12443fd69e9de8bb30" +checksum = "36a8232661d66dcf713394726157d3cfe0a89bfc85f52d6e9f9bbc2306797fe7" dependencies = [ "cc", "codespan-reporting", @@ -858,9 +793,9 @@ dependencies = [ [[package]] name = "cxxbridge-cmd" -version = "1.0.141" +version = "1.0.158" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e43afb0e3b2ef293492a31ecd796af902112460d53e5f923f7804f348a769f9c" +checksum = "4f44296c8693e9ea226a48f6a122727f77aa9e9e338380cb021accaeeb7ee279" dependencies = [ "clap", "codespan-reporting", @@ -871,15 +806,15 @@ dependencies = [ [[package]] name = "cxxbridge-flags" -version = "1.0.141" +version = "1.0.158" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0257ad2096a2474fe877e9e055ab69603851c3d6b394efcc7e0443899c2492ce" +checksum = "c42f69c181c176981ae44ba9876e2ea41ce8e574c296b38d06925ce9214fb8e4" [[package]] name = "cxxbridge-macro" -version = "1.0.141" +version = "1.0.158" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b46cbd7358a46b760609f1cb5093683328e58ca50e594a308716f5403fdc03e5" +checksum = "8faff5d4467e0709448187df29ccbf3b0982cc426ee444a193f87b11afb565a8" dependencies = [ "proc-macro2", "quote", @@ -902,9 +837,9 @@ dependencies = [ [[package]] name = "der" -version = "0.7.9" +version = "0.7.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f55bf8e7b65898637379c1b74eb1551107c8294ed26d855ceb9fd1a09cfc9bc0" +checksum = "e7c1832837b905bbfb5101e07cc24c8deddf52f93225eee6ead5f4d63d53ddcb" dependencies = [ "const-oid", "pem-rfc7468", @@ -913,24 +848,13 @@ dependencies = [ [[package]] name = "deranged" -version = "0.3.11" +version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b42b6fa04a440b495c8b04d0e71b707c585f83cb9cb28cf8cd0d976c315e31b4" +checksum = "9c9e6a11ca8224451684bc0d7d5a7adbf8f2fd6887261a1cfc3c0432f9d4068e" dependencies = [ "powerfmt", ] -[[package]] -name = "derive-new" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d150dea618e920167e5973d70ae6ece4385b7164e0d799fe7c122dd0a5d912ad" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - [[package]] name = "desktop_core" version = "0.0.0" @@ -939,6 +863,7 @@ dependencies = [ "anyhow", "arboard", "argon2", + "ashpd", "base64", "bitwarden-russh", "byteorder", @@ -957,7 +882,7 @@ dependencies = [ "oo7", "pin-project", "pkcs8", - "rand", + "rand 0.9.1", "rsa", "russh-cryptovec", "scopeguard", @@ -967,7 +892,7 @@ dependencies = [ "ssh-encoding", "ssh-key", "sysinfo", - "thiserror 1.0.69", + "thiserror 2.0.12", "tokio", "tokio-stream", "tokio-util", @@ -975,8 +900,9 @@ dependencies = [ "widestring", "windows 0.61.1", "windows-future", - "zbus", + "zbus 4.4.0", "zbus_polkit", + "zeroizing-alloc", ] [[package]] @@ -987,6 +913,7 @@ dependencies = [ "base64", "desktop_core", "hex", + "log", "napi", "napi-build", "napi-derive", @@ -1007,7 +934,7 @@ dependencies = [ "cc", "core-foundation", "glob", - "thiserror 1.0.69", + "thiserror 2.0.12", "tokio", ] @@ -1058,6 +985,27 @@ dependencies = [ "windows-sys 0.59.0", ] +[[package]] +name = "dispatch2" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "89a09f22a6c6069a18470eb92d2298acf25463f14256d24778e1230d789a2aec" +dependencies = [ + "bitflags", + "objc2", +] + +[[package]] +name = "displaydoc" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "doctest-file" version = "1.0.0" @@ -1092,12 +1040,6 @@ dependencies = [ "subtle", ] -[[package]] -name = "either" -version = "1.14.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b7914353092ddf589ad78f25c5c1c21b7f80b0ff8621e7c814c3485b5306da9d" - [[package]] name = "embed_plist" version = "1.2.2" @@ -1139,9 +1081,9 @@ checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" [[package]] name = "errno" -version = "0.3.10" +version = "0.3.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "33d852cb9b869c2a9b3df2f71a3074817f01e1844f839a144f5fcef059a4eb5d" +checksum = "976dd42dc7e85965fe702eb8164f21f450704bdde31faefd6471dba214cb594e" dependencies = [ "libc", "windows-sys 0.59.0", @@ -1149,9 +1091,9 @@ dependencies = [ [[package]] name = "error-code" -version = "3.3.1" +version = "3.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a5d9305ccc6942a704f4335694ecd3de2ea531b114ac2d51f5f843750787a92f" +checksum = "dea2df4cf52843e0452895c455a1a2cfbb842a1e7329671acf418fdc53ed4c59" [[package]] name = "event-listener" @@ -1166,9 +1108,9 @@ dependencies = [ [[package]] name = "event-listener-strategy" -version = "0.5.3" +version = "0.5.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c3e4e0dd3673c1139bf041f3008816d9cf2946bbfac2945c09e523b8d7b05b2" +checksum = "8be9f3dfaaffdae2972880079a491a1a8bb7cbed0b8dd7a347f668b4150a3b93" dependencies = [ "event-listener", "pin-project-lite", @@ -1200,9 +1142,18 @@ checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" [[package]] name = "foldhash" -version = "0.1.4" +version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a0d2fde1f7b3d48b8395d5f2de76c18a528bd6a9cdde438df747bfcba3e05d6f" +checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" + +[[package]] +name = "form_urlencoded" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e13624c2627564efccf4934284bdd98cbaa14e79b0b5a141218e507b3a823456" +dependencies = [ + "percent-encoding", +] [[package]] name = "fs-err" @@ -1337,9 +1288,9 @@ dependencies = [ [[package]] name = "getrandom" -version = "0.2.15" +version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7" +checksum = "335ff9f135e4384c8150d6f27c6daed433577f86b4750418338c01a1a2528592" dependencies = [ "cfg-if", "libc", @@ -1348,14 +1299,14 @@ dependencies = [ [[package]] name = "getrandom" -version = "0.3.1" +version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "43a49c392881ce6d5c3b8cb70f98717b7c07aabbdff06687b9030dbfbe2725f8" +checksum = "26145e563e54f2cadc477553f1ec5ee650b00862f0a58bcd12cbdc5f0ea2d2f4" dependencies = [ "cfg-if", "libc", - "wasi 0.13.3+wasi-0.2.2", - "windows-targets 0.52.6", + "r-efi", + "wasi 0.14.2+wasi-0.2.4", ] [[package]] @@ -1399,9 +1350,9 @@ checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" [[package]] name = "hashbrown" -version = "0.15.2" +version = "0.15.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bf151400ff0baff5465007dd2f3e717f3fe502074ca563069ce3a6629d07b289" +checksum = "84b26c544d002229e640969970a2e74021aadf6e2f96372b9c58eff97de08eb3" [[package]] name = "heck" @@ -1446,19 +1397,126 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5bdbbd5bc8c5749697ccaa352fa45aff8730cf21c68029c0eef1ffed7c3d6ba2" dependencies = [ "cfg-if", - "nix 0.29.0", + "nix", "widestring", "windows 0.57.0", ] [[package]] -name = "indexmap" -version = "2.7.1" +name = "icu_collections" +version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8c9c992b02b5b4c94ea26e32fe5bccb7aa7d9f390ab5c1221ff895bc7ea8b652" +checksum = "200072f5d0e3614556f94a9930d5dc3e0662a652823904c3a75dc3b0af7fee47" +dependencies = [ + "displaydoc", + "potential_utf", + "yoke", + "zerofrom", + "zerovec", +] + +[[package]] +name = "icu_locale_core" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0cde2700ccaed3872079a65fb1a78f6c0a36c91570f28755dda67bc8f7d9f00a" +dependencies = [ + "displaydoc", + "litemap", + "tinystr", + "writeable", + "zerovec", +] + +[[package]] +name = "icu_normalizer" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "436880e8e18df4d7bbc06d58432329d6458cc84531f7ac5f024e93deadb37979" +dependencies = [ + "displaydoc", + "icu_collections", + "icu_normalizer_data", + "icu_properties", + "icu_provider", + "smallvec", + "zerovec", +] + +[[package]] +name = "icu_normalizer_data" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "00210d6893afc98edb752b664b8890f0ef174c8adbb8d0be9710fa66fbbf72d3" + +[[package]] +name = "icu_properties" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2549ca8c7241c82f59c80ba2a6f415d931c5b58d24fb8412caa1a1f02c49139a" +dependencies = [ + "displaydoc", + "icu_collections", + "icu_locale_core", + "icu_properties_data", + "icu_provider", + "potential_utf", + "zerotrie", + "zerovec", +] + +[[package]] +name = "icu_properties_data" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8197e866e47b68f8f7d95249e172903bec06004b18b2937f1095d40a0c57de04" + +[[package]] +name = "icu_provider" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "03c80da27b5f4187909049ee2d72f276f0d9f99a42c306bd0131ecfe04d8e5af" +dependencies = [ + "displaydoc", + "icu_locale_core", + "stable_deref_trait", + "tinystr", + "writeable", + "yoke", + "zerofrom", + "zerotrie", + "zerovec", +] + +[[package]] +name = "idna" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "686f825264d630750a544639377bae737628043f20d38bbc029e8f29ea968a7e" +dependencies = [ + "idna_adapter", + "smallvec", + "utf8_iter", +] + +[[package]] +name = "idna_adapter" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3acae9609540aa318d1bc588455225fb2085b9ed0c4f6bd0d9d5bcd86f1a0344" +dependencies = [ + "icu_normalizer", + "icu_properties", +] + +[[package]] +name = "indexmap" +version = "2.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cea70ddb795996207ad57735b50c5982d8844f38ba9ee5f1aedcfb708a2aa11e" dependencies = [ "equivalent", - "hashbrown 0.15.2", + "hashbrown 0.15.3", ] [[package]] @@ -1492,20 +1550,11 @@ 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" +version = "1.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d75a2a4b1b190afb6f5425f10f6a8f959d2ea0b9c2b1d79553551850539e4674" +checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" [[package]] name = "keytar" @@ -1539,15 +1588,15 @@ 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" -version = "0.8.6" +version = "0.8.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fc2f4eb4bc735547cfed7c0a4922cbd04a4655978c09b54f1f7b228750664c34" +checksum = "6a793df0d7afeac54f95b471d3af7f0d4fb975699f972341a4b76988d49cdf0c" dependencies = [ "cfg-if", "windows-targets 0.52.6", @@ -1555,9 +1604,9 @@ dependencies = [ [[package]] name = "libm" -version = "0.2.11" +version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8355be11b20d696c8f18f6cc018c4e372165b1fa8126cef092399c9951984ffa" +checksum = "f9fbbcab51052fe104eb5e5d351cf728d30a5be1fe14d9be8a3b097481fb97de" [[package]] name = "libredox" @@ -1571,9 +1620,9 @@ dependencies = [ [[package]] name = "link-cplusplus" -version = "1.0.9" +version = "1.0.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9d240c6f7e1ba3a28b0249f774e6a9dd0175054b52dfbb61b16eb8505c3785c9" +checksum = "4a6f6da007f968f9def0d65a05b187e2960183de70c160204ecfccf0ee330212" dependencies = [ "cc", ] @@ -1584,6 +1633,18 @@ version = "0.4.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d26c52dbd32dccf2d10cac7725f8eae5296885fb5703b261f7d0a0739ec807ab" +[[package]] +name = "linux-raw-sys" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd945864f07fe9f5371a27ad7b52a172b4b499999f1d97574c9fa68373937e12" + +[[package]] +name = "litemap" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "241eaef5fd12c88705a01fc1066c48c4b36e0dd4377dcdc7ec3942cea7a69956" + [[package]] name = "lock_api" version = "0.4.12" @@ -1664,9 +1725,9 @@ checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" [[package]] name = "miniz_oxide" -version = "0.8.5" +version = "0.8.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e3e04debbb59698c15bacbb6d93584a8c0ca9cc3213cb423d31f760d8843ce5" +checksum = "3be647b768db090acb35d5ec5db2b0e1f1de11133ca123b9eacf5137868f892a" dependencies = [ "adler2", ] @@ -1684,9 +1745,9 @@ dependencies = [ [[package]] name = "napi" -version = "2.16.15" +version = "2.16.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d3437deb8b6ba2448b6a94260c5c6b9e5eeb5a5d6277e44b40b2532d457b0f0d" +checksum = "55740c4ae1d8696773c78fdafd5d0e5fe9bc9f1b071c7ba493ba5c413a9184f3" dependencies = [ "bitflags", "ctor", @@ -1698,9 +1759,9 @@ dependencies = [ [[package]] name = "napi-build" -version = "2.1.4" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "db836caddef23662b94e16bf1f26c40eceb09d6aee5d5b06a7ac199320b69b19" +checksum = "03acbfa4f156a32188bfa09b86dc11a431b5725253fc1fc6f6df5bed273382c4" [[package]] name = "napi-derive" @@ -1740,18 +1801,6 @@ dependencies = [ "libloading", ] -[[package]] -name = "nix" -version = "0.28.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ab2156c4fce2f8df6c499cc1c763e4394b7482525bf2a9701c9d79d215f519e4" -dependencies = [ - "bitflags", - "cfg-if", - "cfg_aliases 0.1.1", - "libc", -] - [[package]] name = "nix" version = "0.29.0" @@ -1760,7 +1809,7 @@ checksum = "71e2746dc3a24dd78b3cfcb7be93368c6de9963d30f43a6a73998a9cf4b17b46" dependencies = [ "bitflags", "cfg-if", - "cfg_aliases 0.2.1", + "cfg_aliases", "libc", "memoffset", ] @@ -1820,7 +1869,7 @@ dependencies = [ "num-integer", "num-iter", "num-traits", - "rand", + "rand 0.8.5", "serde", "smallvec", "zeroize", @@ -1891,60 +1940,49 @@ dependencies = [ "libc", ] -[[package]] -name = "objc-sys" -version = "0.3.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cdb91bdd390c7ce1a8607f35f3ca7151b65afc0ff5ff3b34fa350f7d7c7e4310" - [[package]] name = "objc2" -version = "0.5.2" +version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "46a785d4eeff09c14c487497c162e92766fbb3e4059a71840cecc03d9a50b804" +checksum = "88c6597e14493ab2e44ce58f2fdecf095a51f12ca57bec060a11c57332520551" dependencies = [ - "objc-sys", "objc2-encode", ] [[package]] name = "objc2-app-kit" -version = "0.2.2" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e4e89ad9e3d7d297152b17d39ed92cd50ca8063a89a9fa569046d41568891eff" +checksum = "e6f29f568bec459b0ddff777cec4fe3fd8666d82d5a40ebd0ff7e66134f89bcc" dependencies = [ "bitflags", - "block2", - "libc", - "objc2", - "objc2-core-data", - "objc2-core-image", - "objc2-foundation", - "objc2-quartz-core", -] - -[[package]] -name = "objc2-core-data" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "617fbf49e071c178c0b24c080767db52958f716d9eabdf0890523aeae54773ef" -dependencies = [ - "bitflags", - "block2", "objc2", + "objc2-core-graphics", "objc2-foundation", ] [[package]] -name = "objc2-core-image" -version = "0.2.2" +name = "objc2-core-foundation" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "55260963a527c99f1819c4f8e3b47fe04f9650694ef348ffd2227e8196d34c80" +checksum = "1c10c2894a6fed806ade6027bcd50662746363a9589d3ec9d9bef30a4e4bc166" dependencies = [ - "block2", + "bitflags", + "dispatch2", "objc2", - "objc2-foundation", - "objc2-metal", +] + +[[package]] +name = "objc2-core-graphics" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "989c6c68c13021b5c2d6b71456ebb0f9dc78d752e86a98da7c716f4f9470f5a4" +dependencies = [ + "bitflags", + "dispatch2", + "objc2", + "objc2-core-foundation", + "objc2-io-surface", ] [[package]] @@ -1955,39 +1993,34 @@ checksum = "ef25abbcd74fb2609453eb695bd2f860d389e457f67dc17cafc8b8cbc89d0c33" [[package]] name = "objc2-foundation" -version = "0.2.2" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ee638a5da3799329310ad4cfa62fbf045d5f56e3ef5ba4149e7452dcf89d5a8" +checksum = "900831247d2fe1a09a683278e5384cfb8c80c79fe6b166f9d14bfdde0ea1b03c" dependencies = [ "bitflags", - "block2", + "objc2", + "objc2-core-foundation", +] + +[[package]] +name = "objc2-io-kit" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "71c1c64d6120e51cd86033f67176b1cb66780c2efe34dec55176f77befd93c0a" +dependencies = [ "libc", - "objc2", + "objc2-core-foundation", ] [[package]] -name = "objc2-metal" -version = "0.2.2" +name = "objc2-io-surface" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dd0cba1276f6023976a406a14ffa85e1fdd19df6b0f737b063b95f6c8c7aadd6" +checksum = "7282e9ac92529fa3457ce90ebb15f4ecbc383e8338060960760fa2cf75420c3c" dependencies = [ "bitflags", - "block2", "objc2", - "objc2-foundation", -] - -[[package]] -name = "objc2-quartz-core" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e42bee7bff906b14b167da2bac5efe6b6a07e6f7c0a21a7308d40c960242dc7a" -dependencies = [ - "bitflags", - "block2", - "objc2", - "objc2-foundation", - "objc2-metal", + "objc2-core-foundation", ] [[package]] @@ -2001,41 +2034,39 @@ dependencies = [ [[package]] name = "once_cell" -version = "1.20.3" +version = "1.21.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "945462a4b81e43c4e3ba96bd7b49d834c6f61198356aa858733bc4acf3cbe62e" +checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" [[package]] name = "oo7" -version = "0.3.3" +version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8fc6ce4692fbfd044ce22ca07dcab1a30fa12432ca2aa5b1294eca50d3332a24" +checksum = "6cb23d3ec3527d65a83be1c1795cb883c52cfa57147d42acc797127df56fc489" dependencies = [ "aes", - "async-fs", - "async-io", - "async-lock", - "async-net", - "blocking", + "ashpd", "cbc", "cipher", "digest", "endi", - "futures-lite", "futures-util", + "getrandom 0.3.3", "hkdf", "hmac", "md-5", "num", "num-bigint-dig", "pbkdf2", - "rand", + "rand 0.9.1", "serde", "sha2", "subtle", - "zbus", + "tokio", + "zbus 5.6.0", + "zbus_macros 5.6.0", "zeroize", - "zvariant", + "zvariant 5.5.1", ] [[package]] @@ -2117,7 +2148,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "346f04948ba92c43e8469c1ee6736c7563d71012b17d40745260fe106aac2166" dependencies = [ "base64ct", - "rand_core", + "rand_core 0.6.4", "subtle", ] @@ -2146,6 +2177,12 @@ dependencies = [ "base64ct", ] +[[package]] +name = "percent-encoding" +version = "2.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" + [[package]] name = "petgraph" version = "0.6.5" @@ -2158,18 +2195,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", @@ -2233,15 +2270,15 @@ checksum = "f950b2377845cebe5cf8b5165cb3cc1a5e0fa5cfa3e1f7f55707d8fd82e0a7b7" dependencies = [ "der", "pkcs5", - "rand_core", + "rand_core 0.6.4", "spki", ] [[package]] name = "pkg-config" -version = "0.3.31" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "953ec861398dccce10c670dfeaf3ec4911ca479e9c02154b3a215178c5f566f2" +checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c" [[package]] name = "plain" @@ -2259,7 +2296,7 @@ dependencies = [ "concurrent-queue", "hermit-abi", "pin-project-lite", - "rustix", + "rustix 0.38.44", "tracing", "windows-sys 0.59.0", ] @@ -2287,6 +2324,15 @@ dependencies = [ "universal-hash", ] +[[package]] +name = "potential_utf" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5a7c30837279ca13e7c867e9e40053bc68740f988cb07f7ca6df43cc734b585" +dependencies = [ + "zerovec", +] + [[package]] name = "powerfmt" version = "0.2.0" @@ -2295,59 +2341,55 @@ checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" [[package]] name = "ppv-lite86" -version = "0.2.20" +version = "0.2.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77957b295656769bb8ad2b6a6b09d897d94f05c41b069aede1fcdaa675eaea04" +checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9" 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" +version = "3.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ecf48c7ca261d60b74ab1a7b20da18bede46776b2e55535cb958eb595c5fa7b" +checksum = "edce586971a4dfaa28950c6f18ed55e0406c1ab88bbce2c6f6293a7aaba73d35" dependencies = [ "toml_edit", ] [[package]] name = "proc-macro2" -version = "1.0.93" +version = "1.0.95" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "60946a68e5f9d28b0dc1c21bb8a97ee7d018a8b322fa57838ba31cc878e22d99" +checksum = "02b3e5e68a3a1a02aad3ec490a98007cbc13c37cbe84a3cd7b8e406d76e7f778" dependencies = [ "unicode-ident", ] [[package]] name = "quick-xml" -version = "0.37.2" +version = "0.37.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "165859e9e55f79d67b96c5d96f4e88b6f2695a1972849c15a6a3f5c59fc2c003" +checksum = "331e97a1af0bf59823e6eadffe373d7b27f485be8748f71471c662c1f269b7fb" dependencies = [ "memchr", ] [[package]] name = "quote" -version = "1.0.38" +version = "1.0.40" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0e4dccaaaf89514f546c693ddc140f729f958c247918a13380cccc6078391acc" +checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d" dependencies = [ "proc-macro2", ] +[[package]] +name = "r-efi" +version = "5.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "74765f6d916ee2faa39bc8e68e4f3ed8949b48cccdac59983d287a7cb71ce9c5" + [[package]] name = "rand" version = "0.8.5" @@ -2355,8 +2397,18 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" dependencies = [ "libc", - "rand_chacha", - "rand_core", + "rand_chacha 0.3.1", + "rand_core 0.6.4", +] + +[[package]] +name = "rand" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9fbfd9d094a40bf3ae768db9361049ace4c0e04a4fd6b359518bd7b73a73dd97" +dependencies = [ + "rand_chacha 0.9.0", + "rand_core 0.9.3", ] [[package]] @@ -2366,7 +2418,17 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" dependencies = [ "ppv-lite86", - "rand_core", + "rand_core 0.6.4", +] + +[[package]] +name = "rand_chacha" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb" +dependencies = [ + "ppv-lite86", + "rand_core 0.9.3", ] [[package]] @@ -2375,27 +2437,16 @@ version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" dependencies = [ - "getrandom 0.2.15", + "getrandom 0.2.16", ] [[package]] -name = "rayon" -version = "1.10.0" +name = "rand_core" +version = "0.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b418a60154510ca1a002a752ca9714984e21e4241e804d32555251faf8b78ffa" +checksum = "99d9a13982dcf210057a8a78572b2217b667c3beacbf3a0d8b454f6f82837d38" dependencies = [ - "either", - "rayon-core", -] - -[[package]] -name = "rayon-core" -version = "1.12.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1465873a3dfdaa8ae7cb14b4383657caab0b3e8a0aa9ae8e04b044854c8dfce2" -dependencies = [ - "crossbeam-deque", - "crossbeam-utils", + "getrandom 0.3.3", ] [[package]] @@ -2406,9 +2457,9 @@ checksum = "d3edd4d5d42c92f0a659926464d4cce56b562761267ecf0f469d85b7de384175" [[package]] name = "redox_syscall" -version = "0.5.9" +version = "0.5.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "82b568323e98e49e2a0899dcee453dd679fae22d69adf9b11dd508d1549b7e2f" +checksum = "928fca9cf2aa042393a8325b9ead81d2f0df4cb12e1e24cef072922ccd99c5af" dependencies = [ "bitflags", ] @@ -2419,9 +2470,9 @@ version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dd6f9d3d47bdd2ad6945c5015a226ec6155d0bcdfd8f7cd29f86b71f8de99d2b" dependencies = [ - "getrandom 0.2.15", + "getrandom 0.2.16", "libredox", - "thiserror 2.0.11", + "thiserror 2.0.12", ] [[package]] @@ -2466,7 +2517,7 @@ dependencies = [ "num-traits", "pkcs1", "pkcs8", - "rand_core", + "rand_core 0.6.4", "sha2", "signature", "spki", @@ -2490,12 +2541,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" @@ -2514,21 +2559,34 @@ dependencies = [ "bitflags", "errno", "libc", - "linux-raw-sys", + "linux-raw-sys 0.4.15", + "windows-sys 0.59.0", +] + +[[package]] +name = "rustix" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c71e83d6afe7ff64890ec6b71d6a69bb8a610ab78ce364b3352876bb4c801266" +dependencies = [ + "bitflags", + "errno", + "libc", + "linux-raw-sys 0.9.4", "windows-sys 0.59.0", ] [[package]] name = "rustversion" -version = "1.0.19" +version = "1.0.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f7c45b9784283f1b2e7fb61b42047c2fd678ef0960d4f6f1eba131594cc369d4" +checksum = "eded382c5f5f786b989652c49544c4877d9f015cc22e145a5ea8ea66c2921cd2" [[package]] name = "ryu" -version = "1.0.19" +version = "1.0.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ea1a2d0a644769cc99faa24c3ad26b379b786fe7c36fd3c546254801650e6dd" +checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f" [[package]] name = "salsa20" @@ -2547,9 +2605,9 @@ checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" [[package]] name = "scratch" -version = "1.0.7" +version = "1.0.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a3cf7c11c38cb994f3d40e8a8cde3bbd1f72a435e4c49e85d6553d8312306152" +checksum = "9f6280af86e5f559536da57a45ebc84948833b3bee313a7dd25232e09c878a52" [[package]] name = "scroll" @@ -2562,9 +2620,9 @@ dependencies = [ [[package]] name = "scroll_derive" -version = "0.12.0" +version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f81c2fde025af7e69b1d1420531c8a8811ca898919db177141a85313b1cb932" +checksum = "1783eabc414609e28a5ba76aee5ddd52199f7107a0b24c2e9746a1ecc34a683d" dependencies = [ "proc-macro2", "quote", @@ -2607,9 +2665,9 @@ dependencies = [ [[package]] name = "semver" -version = "1.0.25" +version = "1.0.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f79dfe2d285b0488816f30e700a7438c5a73d816b5b7d3ac72fbc48b0d185e03" +checksum = "56e6fa9c48d24d85fb3de5ad847117517440f6beceb7798af16b4a87d616b8d0" dependencies = [ "serde", ] @@ -2648,9 +2706,9 @@ dependencies = [ [[package]] name = "serde_repr" -version = "0.1.19" +version = "0.1.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c64451ba24fc7a6a2d60fc75dd9c83c90903b19028d4eff35e88fc1e86564e9" +checksum = "175ee3e80ae9982737ca543e96133087cbd9a485eecc3bc4de9c1a37b47ea59c" dependencies = [ "proc-macro2", "quote", @@ -2687,9 +2745,9 @@ checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" [[package]] name = "signal-hook-registry" -version = "1.4.2" +version = "1.4.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a9e9e0b4211b72e7b8b6e85c807d36c212bdb33ea8587f7569562a84df5465b1" +checksum = "9203b8055f63a2a00e2f593bb0510367fe707d7ff1e5c872de2f537b339e5410" dependencies = [ "libc", ] @@ -2701,7 +2759,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "77549399552de45a898a580c1b41d445bf730df867cc44e6c0233bbc4b8329de" dependencies = [ "digest", - "rand_core", + "rand_core 0.6.4", ] [[package]] @@ -2732,9 +2790,9 @@ dependencies = [ [[package]] name = "smallvec" -version = "1.14.0" +version = "1.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7fcf8323ef1faaee30a44a340193b1ac6814fd9b7b4e88e9d4519a3e4abe1cfd" +checksum = "8917285742e9f3e1683f0a9c4e6b57960b7314d0b08d30d1ecd426713ee2eee9" [[package]] name = "smawk" @@ -2744,9 +2802,9 @@ checksum = "b7c388c1b5e93756d0c740965c41e8822f866621d41acbdf6336a6a168f8840c" [[package]] name = "socket2" -version = "0.5.8" +version = "0.5.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c970269d99b64e60ec3bd6ad27270092a5394c4e309314b18ae3fe575695fbe8" +checksum = "4f5fd57c80058a56cf5c777ab8a126398ece8e442983605d280a44ce79d0edef" dependencies = [ "libc", "windows-sys 0.52.0", @@ -2805,7 +2863,7 @@ dependencies = [ "bcrypt-pbkdf", "ed25519-dalek", "num-bigint-dig", - "rand_core", + "rand_core 0.6.4", "rsa", "sha2", "signature", @@ -2815,6 +2873,12 @@ dependencies = [ "zeroize", ] +[[package]] +name = "stable_deref_trait" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" + [[package]] name = "static_assertions" version = "1.1.0" @@ -2835,9 +2899,9 @@ checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" [[package]] name = "syn" -version = "2.0.98" +version = "2.0.101" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "36147f1a48ae0ec2b5b3bc5b537d267457555a10dc06f3dbc8cb11ba3006d3b1" +checksum = "8ce2b7fc941b3a24138a0a7cf8e858bfc6a992e7978a068a5c760deb0ed43caf" dependencies = [ "proc-macro2", "quote", @@ -2845,30 +2909,40 @@ dependencies = [ ] [[package]] -name = "sysinfo" -version = "0.33.1" +name = "synstructure" +version = "0.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4fc858248ea01b66f19d8e8a6d55f41deaf91e9d495246fd01368d99935c6c01" +checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "sysinfo" +version = "0.35.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b897c8ea620e181c7955369a31be5f48d9a9121cb59fd33ecef9ff2a34323422" dependencies = [ - "core-foundation-sys", "libc", "memchr", "ntapi", - "rayon", - "windows 0.57.0", + "objc2-core-foundation", + "objc2-io-kit", + "windows 0.61.1", ] [[package]] name = "tempfile" -version = "3.17.1" +version = "3.20.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22e5a0acb1f3f55f65cc4a866c361b2fb2a0ff6366785ae6fbb5f85df07ba230" +checksum = "e8a64e3985349f2441a1a9ef0b853f869006c3855f2cda6862a94d26ebb9d6a1" dependencies = [ - "cfg-if", "fastrand", - "getrandom 0.3.1", + "getrandom 0.3.3", "once_cell", - "rustix", + "rustix 1.0.7", "windows-sys 0.59.0", ] @@ -2883,9 +2957,9 @@ dependencies = [ [[package]] name = "textwrap" -version = "0.16.1" +version = "0.16.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "23d434d3f8967a09480fb04132ebe0a3e088c173e6d0ee7897abbdf4eab0f8b9" +checksum = "c13547615a44dc9c452a8a534638acdf07120d4b6847c8178705da06306a3057" dependencies = [ "smawk", ] @@ -2901,11 +2975,11 @@ dependencies = [ [[package]] name = "thiserror" -version = "2.0.11" +version = "2.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d452f284b73e6d76dd36758a0c8684b1d5be31f92b89d07fd5822175732206fc" +checksum = "567b8a2dae586314f7be2a752ec7474332959c6460e02bde30d702a66d488708" dependencies = [ - "thiserror-impl 2.0.11", + "thiserror-impl 2.0.12", ] [[package]] @@ -2921,9 +2995,9 @@ dependencies = [ [[package]] name = "thiserror-impl" -version = "2.0.11" +version = "2.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26afc1baea8a989337eeb52b6e72a039780ce45c3edfcc9c5b9d112feeb173c2" +checksum = "7f7cf42b4507d8ea322120659672cf1b9dbb93f8f2d4ecfd6e51350ff5b17a1d" dependencies = [ "proc-macro2", "quote", @@ -2932,9 +3006,9 @@ dependencies = [ [[package]] name = "time" -version = "0.3.37" +version = "0.3.41" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "35e7868883861bd0e56d9ac6efcaaca0d6d5d82a2a7ec8209ff492c07cf37b21" +checksum = "8a7619e19bc266e0f9c5e6686659d394bc57973859340060a69221e57dbc0c40" dependencies = [ "deranged", "itoa", @@ -2949,33 +3023,45 @@ dependencies = [ [[package]] name = "time-core" -version = "0.1.2" +version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ef927ca75afb808a4d64dd374f00a2adf8d0fcff8e7b184af886c3c87ec4a3f3" +checksum = "c9e9a38711f559d9e3ce1cdb06dd7c5b8ea546bc90052da6d06bb76da74bb07c" [[package]] name = "time-macros" -version = "0.2.19" +version = "0.2.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2834e6017e3e5e4b9834939793b282bc03b37a3336245fa820e35e233e2a85de" +checksum = "3526739392ec93fd8b359c8e98514cb3e8e021beb4e5f597b00a0221f8ed8a49" dependencies = [ "num-conv", "time-core", ] [[package]] -name = "tokio" -version = "1.43.1" +name = "tinystr" +version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "492a604e2fd7f814268a378409e6c92b5525d747d10db9a229723f55a417958c" +checksum = "5d4f6d1145dcb577acf783d4e601bc1d76a13337bb54e6233add580b07344c8b" +dependencies = [ + "displaydoc", + "zerovec", +] + +[[package]] +name = "tokio" +version = "1.45.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2513ca694ef9ede0fb23fe71a4ee4107cb102b9dc1930f6d0fd77aae068ae165" dependencies = [ "backtrace", "bytes", "libc", "mio", "pin-project-lite", + "signal-hook-registry", "socket2", "tokio-macros", + "tracing", "windows-sys 0.52.0", ] @@ -3025,15 +3111,15 @@ dependencies = [ [[package]] name = "toml_datetime" -version = "0.6.8" +version = "0.6.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0dd7358ecb8fc2f8d014bf86f6f638ce72ba252a2c3a2572f2a795f1d23efb41" +checksum = "3da5db5a963e24bc68be8b17b6fa82814bb22ee8660f192bb182771d498f09a3" [[package]] name = "toml_edit" -version = "0.22.24" +version = "0.22.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "17b4795ff5edd201c7cd6dca065ae59972ce77d1b80fa0a84d94950ece7d1474" +checksum = "310068873db2c5b3e7659d2cc35d21855dbafa50d1ce336397c666e3cb08137e" dependencies = [ "indexmap", "toml_datetime", @@ -3086,9 +3172,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" @@ -3109,9 +3195,9 @@ checksum = "75b844d17643ee918803943289730bec8aac480150456169e647ed0b576ba539" [[package]] name = "unicode-ident" -version = "1.0.17" +version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "00e2473a93778eb0bad35909dff6a10d28e63f792f16ed15e404fca9d5eeedbe" +checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512" [[package]] name = "unicode-segmentation" @@ -3121,9 +3207,9 @@ checksum = "f6ccf251212114b54433ec949fd6a7841275f9ada20dddd2f29e9ceea4501493" [[package]] name = "unicode-width" -version = "0.1.14" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7dd6e30e90baa6f72411720665d41d89b9a3d039dc45b8faea1ddd07f617f6af" +checksum = "1fc81956842c57dac11422a97c3b8195a1ff727f06e85c84ed2e8aa277c9a0fd" [[package]] name = "uniffi" @@ -3265,6 +3351,24 @@ dependencies = [ "subtle", ] +[[package]] +name = "url" +version = "2.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32f8b686cadd1473f4bd0117a5d28d36b1ade384ea9b5069a1c40aefed7fda60" +dependencies = [ + "form_urlencoded", + "idna", + "percent-encoding", + "serde", +] + +[[package]] +name = "utf8_iter" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" + [[package]] name = "utf8parse" version = "0.2.2" @@ -3285,43 +3389,43 @@ checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" [[package]] name = "wasi" -version = "0.13.3+wasi-0.2.2" +version = "0.14.2+wasi-0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26816d2e1a4a36a2940b96c5296ce403917633dff8f3440e9b236ed6f6bacad2" +checksum = "9683f9a5a998d873c0d21fcbe3c083009670149a8fab228644b8bd36b2c48cb3" dependencies = [ "wit-bindgen-rt", ] [[package]] name = "wayland-backend" -version = "0.3.8" +version = "0.3.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b7208998eaa3870dad37ec8836979581506e0c5c64c20c9e79e9d2a10d6f47bf" +checksum = "fe770181423e5fc79d3e2a7f4410b7799d5aab1de4372853de3c6aa13ca24121" dependencies = [ "cc", "downcast-rs", - "rustix", + "rustix 0.38.44", "smallvec", "wayland-sys", ] [[package]] name = "wayland-client" -version = "0.31.8" +version = "0.31.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c2120de3d33638aaef5b9f4472bff75f07c56379cf76ea320bd3a3d65ecaf73f" +checksum = "978fa7c67b0847dbd6a9f350ca2569174974cd4082737054dbb7fbb79d7d9a61" dependencies = [ "bitflags", - "rustix", + "rustix 0.38.44", "wayland-backend", "wayland-scanner", ] [[package]] name = "wayland-protocols" -version = "0.31.2" +version = "0.32.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f81f365b8b4a97f422ac0e8737c438024b5951734506b0e1d775c73030561f4" +checksum = "779075454e1e9a521794fed15886323ea0feda3f8b0fc1390f5398141310422a" dependencies = [ "bitflags", "wayland-backend", @@ -3331,9 +3435,9 @@ dependencies = [ [[package]] name = "wayland-protocols-wlr" -version = "0.2.0" +version = "0.3.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ad1f61b76b6c2d8742e10f9ba5c3737f6530b4c243132c2a2ccc8aa96fe25cd6" +checksum = "1cb6cdc73399c0e06504c437fe3cf886f25568dd5454473d565085b36d6a8bbf" dependencies = [ "bitflags", "wayland-backend", @@ -3373,9 +3477,9 @@ dependencies = [ [[package]] name = "widestring" -version = "1.1.0" +version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7219d36b6eac893fa81e84ebe06485e7dcbb616177469b142df14f1f4deb1311" +checksum = "dd7cf3379ca1aac9eea11fba24fd7e315d621f8dfe35c8d7d2be8b793726e07d" [[package]] name = "winapi" @@ -3462,7 +3566,7 @@ dependencies = [ "windows-interface 0.59.1", "windows-link", "windows-result 0.3.2", - "windows-strings 0.4.0", + "windows-strings", ] [[package]] @@ -3537,13 +3641,13 @@ dependencies = [ [[package]] name = "windows-registry" -version = "0.4.0" +version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4286ad90ddb45071efd1a66dfa43eb02dd0dfbae1545ad6cc3c51cf34d7e8ba3" +checksum = "ad1da3e436dc7653dfdf3da67332e22bff09bb0e28b0239e1624499c7830842e" dependencies = [ + "windows-link", "windows-result 0.3.2", - "windows-strings 0.3.1", - "windows-targets 0.53.0", + "windows-strings", ] [[package]] @@ -3564,15 +3668,6 @@ dependencies = [ "windows-link", ] -[[package]] -name = "windows-strings" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87fa48cc5d406560701792be122a10132491cff9d0aeb23583cc2dcafc847319" -dependencies = [ - "windows-link", -] - [[package]] name = "windows-strings" version = "0.4.0" @@ -3624,29 +3719,13 @@ dependencies = [ "windows_aarch64_gnullvm 0.52.6", "windows_aarch64_msvc 0.52.6", "windows_i686_gnu 0.52.6", - "windows_i686_gnullvm 0.52.6", + "windows_i686_gnullvm", "windows_i686_msvc 0.52.6", "windows_x86_64_gnu 0.52.6", "windows_x86_64_gnullvm 0.52.6", "windows_x86_64_msvc 0.52.6", ] -[[package]] -name = "windows-targets" -version = "0.53.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1e4c7e8ceaaf9cb7d7507c974735728ab453b67ef8f18febdd7c11fe59dca8b" -dependencies = [ - "windows_aarch64_gnullvm 0.53.0", - "windows_aarch64_msvc 0.53.0", - "windows_i686_gnu 0.53.0", - "windows_i686_gnullvm 0.53.0", - "windows_i686_msvc 0.53.0", - "windows_x86_64_gnu 0.53.0", - "windows_x86_64_gnullvm 0.53.0", - "windows_x86_64_msvc 0.53.0", -] - [[package]] name = "windows_aarch64_gnullvm" version = "0.48.5" @@ -3659,12 +3738,6 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" -[[package]] -name = "windows_aarch64_gnullvm" -version = "0.53.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "86b8d5f90ddd19cb4a147a5fa63ca848db3df085e25fee3cc10b39b6eebae764" - [[package]] name = "windows_aarch64_msvc" version = "0.48.5" @@ -3677,12 +3750,6 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" -[[package]] -name = "windows_aarch64_msvc" -version = "0.53.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c7651a1f62a11b8cbd5e0d42526e55f2c99886c77e007179efff86c2b137e66c" - [[package]] name = "windows_i686_gnu" version = "0.48.5" @@ -3695,24 +3762,12 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" -[[package]] -name = "windows_i686_gnu" -version = "0.53.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c1dc67659d35f387f5f6c479dc4e28f1d4bb90ddd1a5d3da2e5d97b42d6272c3" - [[package]] name = "windows_i686_gnullvm" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" -[[package]] -name = "windows_i686_gnullvm" -version = "0.53.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ce6ccbdedbf6d6354471319e781c0dfef054c81fbc7cf83f338a4296c0cae11" - [[package]] name = "windows_i686_msvc" version = "0.48.5" @@ -3725,17 +3780,10 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" -[[package]] -name = "windows_i686_msvc" -version = "0.53.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "581fee95406bb13382d2f65cd4a908ca7b1e4c2f1917f143ba16efe98a589b5d" - [[package]] name = "windows_plugin_authenticator" version = "0.0.0" dependencies = [ - "bindgen", "hex", "windows 0.61.1", "windows-core 0.61.0", @@ -3753,12 +3801,6 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" -[[package]] -name = "windows_x86_64_gnu" -version = "0.53.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2e55b5ac9ea33f2fc1716d1742db15574fd6fc8dadc51caab1c16a3d3b4190ba" - [[package]] name = "windows_x86_64_gnullvm" version = "0.48.5" @@ -3771,12 +3813,6 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" -[[package]] -name = "windows_x86_64_gnullvm" -version = "0.53.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0a6e035dd0599267ce1ee132e51c27dd29437f63325753051e71dd9e42406c57" - [[package]] name = "windows_x86_64_msvc" version = "0.48.5" @@ -3789,41 +3825,34 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" -[[package]] -name = "windows_x86_64_msvc" -version = "0.53.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "271414315aff87387382ec3d271b52d7ae78726f5d44ac98b4f4030c91880486" - [[package]] name = "winnow" -version = "0.7.3" +version = "0.7.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0e7f4ea97f6f78012141bcdb6a216b2609f0979ada50b20ca5b52dde2eac2bb1" +checksum = "c06928c8748d81b05c9be96aad92e1b6ff01833332f281e8cfca3be4b35fc9ec" dependencies = [ "memchr", ] [[package]] name = "wit-bindgen-rt" -version = "0.33.0" +version = "0.39.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3268f3d866458b787f390cf61f4bbb563b922d091359f9608842999eaee3943c" +checksum = "6f42320e61fe2cfd34354ecb597f86f413484a798ba44a8ca1165c58d42da6c1" dependencies = [ "bitflags", ] [[package]] name = "wl-clipboard-rs" -version = "0.8.1" +version = "0.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "12b41773911497b18ca8553c3daaf8ec9fe9819caf93d451d3055f69de028adb" +checksum = "2a083daad7e8a4b8805ad73947ccadabe62afe37ce0e9787a56ff373d34762c7" dependencies = [ - "derive-new", "libc", "log", - "nix 0.28.0", "os_pipe", + "rustix 0.38.44", "tempfile", "thiserror 1.0.69", "tree_magic_mini", @@ -3833,6 +3862,12 @@ dependencies = [ "wayland-protocols-wlr", ] +[[package]] +name = "writeable" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea2f10b9bb0928dfb1b42b65e1f9e36f7f54dbdf08457afefb38afcdec4fa2bb" + [[package]] name = "x11rb" version = "0.13.1" @@ -3840,7 +3875,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5d91ffca73ee7f68ce055750bf9f6eca0780b8c85eff9bc046a3b0da41755e12" dependencies = [ "gethostname", - "rustix", + "rustix 0.38.44", "x11rb-protocol", ] @@ -3860,6 +3895,30 @@ dependencies = [ "windows-sys 0.59.0", ] +[[package]] +name = "yoke" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f41bb01b8226ef4bfd589436a297c53d118f65921786300e427be8d487695cc" +dependencies = [ + "serde", + "stable_deref_trait", + "yoke-derive", + "zerofrom", +] + +[[package]] +name = "yoke-derive" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38da3c9736e16c5d3c8c597a9aaa5d1fa565d0532ae05e27c24aa62fb32c0ab6" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "synstructure", +] + [[package]] name = "zbus" version = "4.4.0" @@ -3882,9 +3941,9 @@ dependencies = [ "futures-sink", "futures-util", "hex", - "nix 0.29.0", + "nix", "ordered-stream", - "rand", + "rand 0.8.5", "serde", "serde_repr", "sha1", @@ -3893,9 +3952,37 @@ dependencies = [ "uds_windows", "windows-sys 0.52.0", "xdg-home", - "zbus_macros", - "zbus_names", - "zvariant", + "zbus_macros 4.4.0", + "zbus_names 3.0.0", + "zvariant 4.2.0", +] + +[[package]] +name = "zbus" +version = "5.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2522b82023923eecb0b366da727ec883ace092e7887b61d3da5139f26b44da58" +dependencies = [ + "async-broadcast", + "async-recursion", + "async-trait", + "enumflags2", + "event-listener", + "futures-core", + "futures-lite", + "hex", + "nix", + "ordered-stream", + "serde", + "serde_repr", + "tokio", + "tracing", + "uds_windows", + "windows-sys 0.59.0", + "winnow", + "zbus_macros 5.6.0", + "zbus_names 4.2.0", + "zvariant 5.5.1", ] [[package]] @@ -3908,7 +3995,22 @@ dependencies = [ "proc-macro2", "quote", "syn", - "zvariant_utils", + "zvariant_utils 2.1.0", +] + +[[package]] +name = "zbus_macros" +version = "5.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05d2e12843c75108c00c618c2e8ef9675b50b6ec095b36dc965f2e5aed463c15" +dependencies = [ + "proc-macro-crate", + "proc-macro2", + "quote", + "syn", + "zbus_names 4.2.0", + "zvariant 5.5.1", + "zvariant_utils 3.2.0", ] [[package]] @@ -3919,7 +4021,19 @@ checksum = "4b9b1fef7d021261cc16cba64c351d291b715febe0fa10dc3a443ac5a5022e6c" dependencies = [ "serde", "static_assertions", - "zvariant", + "zvariant 4.2.0", +] + +[[package]] +name = "zbus_names" +version = "4.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7be68e64bf6ce8db94f63e72f0c7eb9a60d733f7e0499e628dfab0f84d6bcb97" +dependencies = [ + "serde", + "static_assertions", + "winnow", + "zvariant 5.5.1", ] [[package]] @@ -3932,30 +4046,50 @@ dependencies = [ "serde", "serde_repr", "static_assertions", - "zbus", + "zbus 4.4.0", ] [[package]] name = "zerocopy" -version = "0.7.35" +version = "0.8.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b9b4fd18abc82b8136838da5d50bae7bdea537c574d8dc1a34ed098d6c166f0" +checksum = "a1702d9583232ddb9174e01bb7c15a2ab8fb1bc6f227aa1233858c351a3ba0cb" dependencies = [ - "byteorder", "zerocopy-derive", ] [[package]] name = "zerocopy-derive" -version = "0.7.35" +version = "0.8.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e" +checksum = "28a6e20d751156648aa063f3800b706ee209a32c0b4d9f24be3d980b01be55ef" dependencies = [ "proc-macro2", "quote", "syn", ] +[[package]] +name = "zerofrom" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "50cc42e0333e05660c3587f3bf9d0478688e15d870fab3346451ce7f8c9fbea5" +dependencies = [ + "zerofrom-derive", +] + +[[package]] +name = "zerofrom-derive" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d71e5d6e06ab090c67b5e44993ec16b72dcbaabc526db883a360057678b48502" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "synstructure", +] + [[package]] name = "zeroize" version = "1.8.1" @@ -3976,6 +4110,45 @@ dependencies = [ "syn", ] +[[package]] +name = "zeroizing-alloc" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebff5e6b81c1c7dca2d0bd333b2006da48cb37dbcae5a8da888f31fcb3c19934" + +[[package]] +name = "zerotrie" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "36f0bbd478583f79edad978b407914f61b2972f5af6fa089686016be8f9af595" +dependencies = [ + "displaydoc", + "yoke", + "zerofrom", +] + +[[package]] +name = "zerovec" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4a05eb080e015ba39cc9e23bbe5e7fb04d5fb040350f99f34e338d5fdd294428" +dependencies = [ + "yoke", + "zerofrom", + "zerovec-derive", +] + +[[package]] +name = "zerovec-derive" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b96237efa0c878c64bd89c436f661be4e46b2f3eff1ebb976f7ef2321d2f58f" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "zvariant" version = "4.2.0" @@ -3986,7 +4159,22 @@ dependencies = [ "enumflags2", "serde", "static_assertions", - "zvariant_derive", + "zvariant_derive 4.2.0", +] + +[[package]] +name = "zvariant" +version = "5.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "557e89d54880377a507c94cd5452f20e35d14325faf9d2958ebeadce0966c1b2" +dependencies = [ + "endi", + "enumflags2", + "serde", + "url", + "winnow", + "zvariant_derive 5.5.1", + "zvariant_utils 3.2.0", ] [[package]] @@ -3999,7 +4187,20 @@ dependencies = [ "proc-macro2", "quote", "syn", - "zvariant_utils", + "zvariant_utils 2.1.0", +] + +[[package]] +name = "zvariant_derive" +version = "5.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "757779842a0d242061d24c28be589ce392e45350dfb9186dfd7a042a2e19870c" +dependencies = [ + "proc-macro-crate", + "proc-macro2", + "quote", + "syn", + "zvariant_utils 3.2.0", ] [[package]] @@ -4012,3 +4213,17 @@ dependencies = [ "quote", "syn", ] + +[[package]] +name = "zvariant_utils" +version = "3.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e16edfee43e5d7b553b77872d99bc36afdda75c223ca7ad5e3fbecd82ca5fc34" +dependencies = [ + "proc-macro2", + "quote", + "serde", + "static_assertions", + "syn", + "winnow", +] diff --git a/apps/desktop/desktop_native/Cargo.toml b/apps/desktop/desktop_native/Cargo.toml index 01838359147..451704f91fe 100644 --- a/apps/desktop/desktop_native/Cargo.toml +++ b/apps/desktop/desktop_native/Cargo.toml @@ -11,13 +11,14 @@ publish = false [workspace.dependencies] aes = "=0.8.4" anyhow = "=1.0.94" -arboard = { version = "=3.4.1", default-features = false } +arboard = { version = "=3.5.0", default-features = false } argon2 = "=0.5.3" +ashpd = "=0.11.0" base64 = "=0.22.1" -bindgen = "0.71.1" +bindgen = "=0.71.1" bitwarden-russh = { git = "https://github.com/bitwarden/bitwarden-russh.git", rev = "3d48f140fd506412d186203238993163a8c4e536" } byteorder = "=1.5.0" -bytes = "1.9.0" +bytes = "=1.10.1" cbc = "=0.1.2" core-foundation = "=0.10.0" dirs = "=6.0.0" @@ -28,16 +29,16 @@ 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 = "=2.16.17" +napi-build = "=2.2.0" napi-derive = "=2.16.13" -oo7 = "=0.3.3" +oo7 = "=0.4.3" oslog = "=0.2.0" -pin-project = "=1.1.8" +pin-project = "=1.1.10" pkcs8 = "=0.10.2" -rand = "=0.8.5" +rand = "=0.9.1" rsa = "=0.9.6" russh-cryptovec = "=0.7.3" scopeguard = "=1.2.0" @@ -49,17 +50,18 @@ sha2 = "=0.10.8" simplelog = "=0.12.2" ssh-encoding = "=0.2.0" ssh-key = {version = "=0.6.7", default-features = false } -sysinfo = "0.33.1" -thiserror = "=1.0.69" -tokio = "=1.43.1" +sysinfo = "=0.35.0" +thiserror = "=2.0.12" +tokio = "=1.45.0" 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" +widestring = "=1.2.0" windows = "=0.61.1" windows-core = "=0.61.0" windows-future = "=0.2.0" -windows-registry = "=0.4.0" -zbus = "=4.4.0" -zbus_polkit = "=4.0.0" +windows-registry = "=0.5.1" +zbus = "=5.5.0" +zbus_polkit = "=5.0.0" +zeroizing-alloc = "=0.1.0" diff --git a/apps/desktop/desktop_native/build.js b/apps/desktop/desktop_native/build.js index ec20dce4116..2edd0e89616 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,25 @@ 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}`)); + } +} + +function installTarget(target) { + child_process.execSync(`rustup target add ${target}`, { stdio: 'inherit', cwd: __dirname }); } if (!crossPlatform && !target) { @@ -31,50 +58,24 @@ if (!crossPlatform && !target) { if (target) { console.log(`Building for target: ${target} in ${mode} mode`); + installTarget(target); buildNapiModule(target, mode === "release"); buildProxyBin(target, mode === "release"); 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, _]) => { + installTarget(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/core/Cargo.toml b/apps/desktop/desktop_native/core/Cargo.toml index a8a8e0a2a44..7cd67dbad6a 100644 --- a/apps/desktop/desktop_native/core/Cargo.toml +++ b/apps/desktop/desktop_native/core/Cargo.toml @@ -55,6 +55,7 @@ rsa = { workspace = true } ed25519 = { workspace = true, features = ["pkcs8"] } bytes = { workspace = true } sysinfo = { workspace = true, features = ["windows"] } +zeroizing-alloc = { workspace = true } [target.'cfg(windows)'.dependencies] widestring = { workspace = true, optional = true } @@ -84,6 +85,7 @@ desktop_objc = { path = "../objc" } [target.'cfg(target_os = "linux")'.dependencies] oo7 = { workspace = true } libc = { workspace = true } +ashpd = { workspace = true } zbus = { workspace = true, optional = true } zbus_polkit = { workspace = true, optional = true } diff --git a/apps/desktop/desktop_native/core/src/autostart/linux.rs b/apps/desktop/desktop_native/core/src/autostart/linux.rs new file mode 100644 index 00000000000..1fd02a6ea5d --- /dev/null +++ b/apps/desktop/desktop_native/core/src/autostart/linux.rs @@ -0,0 +1,21 @@ +use anyhow::Result; +use ashpd::desktop::background::Background; + +pub async fn set_autostart(autostart: bool, params: Vec) -> Result<()> { + let request = if params.is_empty() { + Background::request().auto_start(autostart) + } else { + Background::request().command(params).auto_start(autostart) + }; + + match request.send().await.and_then(|r| r.response()) { + Ok(response) => { + println!("[ASHPD] Autostart enabled: {:?}", response); + Ok(()) + } + Err(err) => { + println!("[ASHPD] Error enabling autostart: {}", err); + Err(anyhow::anyhow!("error enabling autostart {}", err)) + } + } +} diff --git a/apps/desktop/desktop_native/core/src/autostart/mod.rs b/apps/desktop/desktop_native/core/src/autostart/mod.rs new file mode 100644 index 00000000000..78e27eb433e --- /dev/null +++ b/apps/desktop/desktop_native/core/src/autostart/mod.rs @@ -0,0 +1,5 @@ +#[cfg_attr(target_os = "linux", path = "linux.rs")] +#[cfg_attr(target_os = "windows", path = "unimplemented.rs")] +#[cfg_attr(target_os = "macos", path = "unimplemented.rs")] +mod autostart_impl; +pub use autostart_impl::*; diff --git a/apps/desktop/desktop_native/core/src/autostart/unimplemented.rs b/apps/desktop/desktop_native/core/src/autostart/unimplemented.rs new file mode 100644 index 00000000000..14f567bdc65 --- /dev/null +++ b/apps/desktop/desktop_native/core/src/autostart/unimplemented.rs @@ -0,0 +1,5 @@ +use anyhow::Result; + +pub async fn set_autostart(_autostart: bool, _params: Vec) -> Result<()> { + unimplemented!(); +} diff --git a/apps/desktop/desktop_native/core/src/biometric/unix.rs b/apps/desktop/desktop_native/core/src/biometric/unix.rs index e57b77515e3..60392adc9d7 100644 --- a/apps/desktop/desktop_native/core/src/biometric/unix.rs +++ b/apps/desktop/desktop_native/core/src/biometric/unix.rs @@ -104,6 +104,6 @@ impl super::BiometricTrait for Biometric { fn random_challenge() -> [u8; 16] { let mut challenge = [0u8; 16]; - rand::thread_rng().fill_bytes(&mut challenge); + rand::rng().fill_bytes(&mut challenge); challenge } diff --git a/apps/desktop/desktop_native/core/src/biometric/windows.rs b/apps/desktop/desktop_native/core/src/biometric/windows.rs index b9fea345c16..4c2e2c8ae25 100644 --- a/apps/desktop/desktop_native/core/src/biometric/windows.rs +++ b/apps/desktop/desktop_native/core/src/biometric/windows.rs @@ -174,7 +174,7 @@ impl super::BiometricTrait for Biometric { fn random_challenge() -> [u8; 16] { let mut challenge = [0u8; 16]; - rand::thread_rng().fill_bytes(&mut challenge); + rand::rng().fill_bytes(&mut challenge); challenge } diff --git a/apps/desktop/desktop_native/core/src/lib.rs b/apps/desktop/desktop_native/core/src/lib.rs index 4a6686cc1f5..a72ec04e9c2 100644 --- a/apps/desktop/desktop_native/core/src/lib.rs +++ b/apps/desktop/desktop_native/core/src/lib.rs @@ -1,4 +1,5 @@ pub mod autofill; +pub mod autostart; pub mod biometric; pub mod clipboard; pub mod crypto; @@ -8,3 +9,8 @@ pub mod password; pub mod powermonitor; pub mod process_isolation; pub mod ssh_agent; + +use zeroizing_alloc::ZeroAlloc; + +#[global_allocator] +static ALLOC: ZeroAlloc = ZeroAlloc(std::alloc::System); diff --git a/apps/desktop/desktop_native/core/src/powermonitor/linux.rs b/apps/desktop/desktop_native/core/src/powermonitor/linux.rs index 7d0fde15ed4..aa93037e95f 100644 --- a/apps/desktop/desktop_native/core/src/powermonitor/linux.rs +++ b/apps/desktop/desktop_native/core/src/powermonitor/linux.rs @@ -1,6 +1,8 @@ use std::borrow::Cow; -use zbus::{export::futures_util::TryStreamExt, Connection, MatchRule}; +use futures::TryStreamExt; +use zbus::{Connection, MatchRule}; + struct ScreenLock { interface: Cow<'static, str>, path: Cow<'static, str>, @@ -23,7 +25,7 @@ pub async fn on_lock(tx: tokio::sync::mpsc::Sender<()>) -> Result<(), Box): Promise +} export declare namespace autofill { export function runCommand(value: string): Promise export const enum UserVerification { @@ -185,3 +188,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..079872a3b03 100644 --- a/apps/desktop/desktop_native/napi/src/lib.rs +++ b/apps/desktop/desktop_native/napi/src/lib.rs @@ -477,6 +477,16 @@ pub mod ipc { } } +#[napi] +pub mod autostart { + #[napi] + pub async fn set_autostart(autostart: bool, params: Vec) -> napi::Result<()> { + desktop_core::autostart::set_autostart(autostart, params) + .await + .map_err(|e| napi::Error::from_reason(format!("Error setting autostart - {e} - {e:?}"))) + } +} + #[napi] pub mod autofill { use desktop_core::ipc::server::{Message, MessageType}; @@ -807,3 +817,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/rust-toolchain.toml b/apps/desktop/desktop_native/rust-toolchain.toml new file mode 100644 index 00000000000..898a61f3f4b --- /dev/null +++ b/apps/desktop/desktop_native/rust-toolchain.toml @@ -0,0 +1,4 @@ +[toolchain] +channel = "1.85.0" +components = [ "rustfmt", "clippy" ] +profile = "minimal" 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..35831dad41a 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", @@ -20,7 +20,7 @@ "**/node_modules/@bitwarden/desktop-napi/index.js", "**/node_modules/@bitwarden/desktop-napi/desktop_napi.${platform}-${arch}*.node" ], - "electronVersion": "34.0.0", + "electronVersion": "36.3.1", "generateUpdatesFilesForAllChannels": true, "publish": { "provider": "generic", @@ -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,13 +79,19 @@ "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": { "electronUpdaterCompatibility": ">=0.0.1", "target": ["portable", "nsis-web", "appx"], - "sign": "./sign.js", + "signtoolOptions": { + "sign": "./sign.js" + }, "extraFiles": [ { "from": "desktop_native/dist/desktop_proxy.${platform}-${arch}.exe", @@ -103,9 +110,11 @@ ], "target": ["deb", "freebsd", "rpm", "AppImage", "snap"], "desktop": { - "Name": "Bitwarden", - "Type": "Application", - "GenericName": "Password Manager" + "entry": { + "Name": "Bitwarden", + "Type": "Application", + "GenericName": "Password Manager" + } } }, "dmg": { @@ -137,7 +146,8 @@ "extendInfo": { "LSMinimumSystemVersion": "12", "ElectronTeamID": "LTZ2PFU5D6" - } + }, + "provisioningProfile": "bitwarden_desktop_appstore.provisionprofile" }, "nsisWeb": { "oneClick": false, @@ -237,7 +247,7 @@ }, "snap": { "summary": "Bitwarden is a secure and free password manager for all of your devices.", - "description": "**Installation**\nBitwarden requires access to the `password-manager-service`. Please enable it through permissions or by running `sudo snap connect bitwarden:password-manager-service` after installation. See https://btwrdn.com/install-snap for details.", + "description": "Password Manager\n**Installation**\nBitwarden requires access to the `password-manager-service`. Please enable it through permissions or by running `sudo snap connect bitwarden:password-manager-service` after installation. See https://btwrdn.com/install-snap for details.", "autoStart": true, "base": "core22", "confinement": "strict", diff --git a/apps/desktop/jest.config.js b/apps/desktop/jest.config.js index 73f5ada287a..14cd959810e 100644 --- a/apps/desktop/jest.config.js +++ b/apps/desktop/jest.config.js @@ -1,18 +1,17 @@ const { pathsToModuleNameMapper } = require("ts-jest"); -const { compilerOptions } = require("./tsconfig"); +const { compilerOptions } = require("../../tsconfig.base"); const sharedConfig = require("../../libs/shared/jest.config.angular"); /** @type {import('jest').Config} */ module.exports = { ...sharedConfig, - preset: "jest-preset-angular", setupFilesAfterEnv: ["/test.setup.ts"], moduleNameMapper: pathsToModuleNameMapper( - { "@bitwarden/common/spec": ["../../libs/common/spec"], ...(compilerOptions?.paths ?? {}) }, + { "@bitwarden/common/spec": ["libs/common/spec"], ...(compilerOptions?.paths ?? {}) }, { - prefix: "/", + prefix: "/../../", }, ), }; 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..37b8cf96ff3 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.15.3", "typescript": "5.4.2" } }, @@ -101,18 +101,18 @@ "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.15.3", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.15.3.tgz", + "integrity": "sha512-lX7HFZeHf4QG/J7tBZqrCAXwz9J5RD56Y6MpP0eJkka8p+K0RY/yBTW7CYFJ4VGCclxqOLKmiGP5juQc6MKgcw==", "license": "MIT", "dependencies": { - "undici-types": "~6.20.0" + "undici-types": "~6.21.0" } }, "node_modules/acorn": { - "version": "8.14.0", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.14.0.tgz", - "integrity": "sha512-cl669nCJTZBsL97OF4kUQm5g5hC2uihk0NxY3WENAC0TYdILVkAyHymAntgxGkl7K+t0cXIrH5siy5S4XkFycA==", + "version": "8.14.1", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.14.1.tgz", + "integrity": "sha512-OvQ/2pUDKmgfCg++xsTX1wGxfTaszcHVcTctW4UJB4hibJx2HXxxO5UmVgyjMa+ZDsiaf5wWLXYpRWMmBI0QHg==", "license": "MIT", "bin": { "acorn": "bin/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..ea6b1b3e7a8 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.15.3", "typescript": "5.4.2" }, "_moduleAliases": { diff --git a/apps/desktop/native-messaging-test-runner/src/ipc.service.ts b/apps/desktop/native-messaging-test-runner/src/ipc.service.ts index b02ff1a4225..d8616e9757a 100644 --- a/apps/desktop/native-messaging-test-runner/src/ipc.service.ts +++ b/apps/desktop/native-messaging-test-runner/src/ipc.service.ts @@ -15,6 +15,8 @@ const DEFAULT_MESSAGE_TIMEOUT = 10 * 1000; // 10 seconds export type MessageHandler = (MessageCommon) => void; +// FIXME: update to use a const object instead of a typescript enum +// eslint-disable-next-line @bitwarden/platform/no-enums export enum IPCConnectionState { Disconnected = "disconnected", Connecting = "connecting", diff --git a/apps/desktop/native-messaging-test-runner/src/native-message.service.ts b/apps/desktop/native-messaging-test-runner/src/native-message.service.ts index c01d581afe8..c2356f93e28 100644 --- a/apps/desktop/native-messaging-test-runner/src/native-message.service.ts +++ b/apps/desktop/native-messaging-test-runner/src/native-message.service.ts @@ -220,7 +220,7 @@ export default class NativeMessageService { const sharedKey = await this.getSharedKeyForKey(key); - return this.encryptService.encrypt(commandDataString, sharedKey); + return this.encryptService.encryptString(commandDataString, sharedKey); } private async decryptResponsePayload( @@ -228,11 +228,7 @@ export default class NativeMessageService { key: string, ): Promise { const sharedKey = await this.getSharedKeyForKey(key); - const decrypted = await this.encryptService.decryptToUtf8( - payload, - sharedKey, - "native-messaging-session", - ); + const decrypted = await this.encryptService.decryptString(payload, sharedKey); return JSON.parse(decrypted); } diff --git a/apps/desktop/native-messaging-test-runner/tsconfig.json b/apps/desktop/native-messaging-test-runner/tsconfig.json index 59c7040e509..608e5a3bf4c 100644 --- a/apps/desktop/native-messaging-test-runner/tsconfig.json +++ b/apps/desktop/native-messaging-test-runner/tsconfig.json @@ -1,6 +1,6 @@ { + "extends": "../tsconfig", "compilerOptions": { - "baseUrl": "./", "outDir": "dist", "target": "es6", "module": "CommonJS", @@ -10,12 +10,7 @@ "sourceMap": false, "declaration": false, "paths": { - "@src/*": ["src/*"], - "@bitwarden/admin-console/*": ["../../../libs/admin-console/src/*"], - "@bitwarden/auth/*": ["../../../libs/auth/src/*"], - "@bitwarden/common/*": ["../../../libs/common/src/*"], - "@bitwarden/key-management": ["../../../libs/key-management/src/"], - "@bitwarden/node/*": ["../../../libs/node/src/*"] + "@src/*": ["src/*"] }, "plugins": [ { diff --git a/apps/desktop/package.json b/apps/desktop/package.json index ae34deee5b8..94568476179 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.6.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:win": "npm run clean:dist && electron-builder --win --x64 --arm64 --ia32 -p never -c.win.certificateSubjectName=\"8bit Solutions LLC\"", + "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.signtoolOptions.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", @@ -56,7 +62,7 @@ "publish:lin": "npm run build && npm run clean:dist && electron-builder --linux --x64 -p always", "publish:mac": "npm run build && npm run clean:dist && electron-builder --mac -p always", "publish:mac:mas": "npm run dist:mac:mas && npm run upload:mas", - "publish:win": "npm run build && npm run clean:dist && electron-builder --win --x64 --arm64 --ia32 -p always -c.win.certificateSubjectName=\"8bit Solutions LLC\"", + "publish:win": "npm run build && npm run clean:dist && electron-builder --win --x64 --arm64 --ia32 -p always -c.win.signtoolOptions.certificateSubjectName=\"8bit Solutions LLC\"", "publish:win:dev": "npm run build && npm run clean:dist && electron-builder --win --x64 --arm64 --ia32 -p always", "upload:mas": "xcrun altool --upload-app --type osx --file \"$(find ./dist/mas-universal/Bitwarden*.pkg)\" --apiKey $APP_STORE_CONNECT_AUTH_KEY --apiIssuer $APP_STORE_CONNECT_TEAM_ISSUER", "test": "jest", 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/accounts/settings.component.html b/apps/desktop/src/app/accounts/settings.component.html index bcbd9969f96..e56615c9122 100644 --- a/apps/desktop/src/app/accounts/settings.component.html +++ b/apps/desktop/src/app/accounts/settings.component.html @@ -440,6 +440,22 @@ "enableSshAgentDesc" | i18n }}
+
+ + + {{ + "sshAgentPromptBehaviorDesc" | i18n + }} +
diff --git a/apps/desktop/src/vault/app/vault/add-edit-custom-fields.component.ts b/apps/desktop/src/vault/app/vault/add-edit-custom-fields.component.ts index 6992455a8a6..b4be2406c4b 100644 --- a/apps/desktop/src/vault/app/vault/add-edit-custom-fields.component.ts +++ b/apps/desktop/src/vault/app/vault/add-edit-custom-fields.component.ts @@ -7,6 +7,7 @@ import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.servic @Component({ selector: "app-vault-add-edit-custom-fields", templateUrl: "add-edit-custom-fields.component.html", + standalone: false, }) export class AddEditCustomFieldsComponent extends BaseAddEditCustomFieldsComponent { constructor(i18nService: I18nService, eventCollectionService: EventCollectionService) { diff --git a/apps/desktop/src/vault/app/vault/add-edit.component.html b/apps/desktop/src/vault/app/vault/add-edit.component.html index 7904d7b7f34..9c316813d1d 100644 --- a/apps/desktop/src/vault/app/vault/add-edit.component.html +++ b/apps/desktop/src/vault/app/vault/add-edit.component.html @@ -519,7 +519,7 @@ appA11yTitle="{{ 'importSshKeyFromClipboard' | i18n }}" (click)="importSshKeyFromClipboard()" > - +
@@ -788,7 +788,13 @@ diff --git a/apps/desktop/src/vault/app/vault/add-edit.component.ts b/apps/desktop/src/vault/app/vault/add-edit.component.ts index 2c8b5a8321a..eb04003a418 100644 --- a/apps/desktop/src/vault/app/vault/add-edit.component.ts +++ b/apps/desktop/src/vault/app/vault/add-edit.component.ts @@ -30,6 +30,7 @@ const BroadcasterSubscriptionId = "AddEditComponent"; @Component({ selector: "app-vault-add-edit", templateUrl: "add-edit.component.html", + standalone: false, }) export class AddEditComponent extends BaseAddEditComponent implements OnInit, OnChanges, OnDestroy { @ViewChild("form") diff --git a/apps/desktop/src/vault/app/vault/attachments.component.ts b/apps/desktop/src/vault/app/vault/attachments.component.ts index ea4f49b8431..a116a4d2acb 100644 --- a/apps/desktop/src/vault/app/vault/attachments.component.ts +++ b/apps/desktop/src/vault/app/vault/attachments.component.ts @@ -5,6 +5,7 @@ import { ApiService } from "@bitwarden/common/abstractions/api.service"; import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; import { BillingAccountProfileStateService } from "@bitwarden/common/billing/abstractions/account/billing-account-profile-state.service"; import { EncryptService } from "@bitwarden/common/key-management/crypto/abstractions/encrypt.service"; +import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; import { FileDownloadService } from "@bitwarden/common/platform/abstractions/file-download/file-download.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; @@ -17,6 +18,7 @@ import { KeyService } from "@bitwarden/key-management"; @Component({ selector: "app-vault-attachments", templateUrl: "attachments.component.html", + standalone: false, }) export class AttachmentsComponent extends BaseAttachmentsComponent { constructor( @@ -33,6 +35,7 @@ export class AttachmentsComponent extends BaseAttachmentsComponent { billingAccountProfileStateService: BillingAccountProfileStateService, accountService: AccountService, toastService: ToastService, + configService: ConfigService, ) { super( cipherService, @@ -49,6 +52,7 @@ export class AttachmentsComponent extends BaseAttachmentsComponent { billingAccountProfileStateService, accountService, toastService, + configService, ); } } diff --git a/apps/desktop/src/vault/app/vault/collections.component.ts b/apps/desktop/src/vault/app/vault/collections.component.ts index e7684c3c07a..46455d04cd2 100644 --- a/apps/desktop/src/vault/app/vault/collections.component.ts +++ b/apps/desktop/src/vault/app/vault/collections.component.ts @@ -13,6 +13,7 @@ import { ToastService } from "@bitwarden/components"; @Component({ selector: "app-vault-collections", templateUrl: "collections.component.html", + standalone: false, }) export class CollectionsComponent extends BaseCollectionsComponent { constructor( 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..26349920106 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 @@ -3,6 +3,7 @@ import { Component, Inject } from "@angular/core"; import { JslibModule } from "@bitwarden/angular/jslib.module"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; +import { UnionOfValues } from "@bitwarden/common/vault/types/union-of-values"; import { DIALOG_DATA, ButtonModule, @@ -10,6 +11,7 @@ import { DialogService, ItemModule, LinkModule, + DialogRef, } from "@bitwarden/components"; import { CredentialGeneratorHistoryDialogComponent, @@ -19,12 +21,25 @@ 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 const CredentialGeneratorDialogAction = { + Selected: "selected", + Canceled: "canceled", +} as const; + +type CredentialGeneratorDialogAction = UnionOfValues; + @Component({ - standalone: true, selector: "credential-generator-dialog", templateUrl: "credential-generator-dialog.component.html", imports: [ @@ -45,6 +60,7 @@ export class CredentialGeneratorDialogComponent { constructor( @Inject(DIALOG_DATA) protected data: CredentialGeneratorParams, private dialogService: DialogService, + private dialogRef: DialogRef, private i18nService: I18nService, ) {} @@ -59,11 +75,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 +95,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/folder-add-edit.component.ts b/apps/desktop/src/vault/app/vault/folder-add-edit.component.ts index cdb879693c0..cecd5cd671c 100644 --- a/apps/desktop/src/vault/app/vault/folder-add-edit.component.ts +++ b/apps/desktop/src/vault/app/vault/folder-add-edit.component.ts @@ -14,6 +14,7 @@ import { KeyService } from "@bitwarden/key-management"; @Component({ selector: "app-folder-add-edit", templateUrl: "folder-add-edit.component.html", + standalone: false, }) export class FolderAddEditComponent extends BaseFolderAddEditComponent { constructor( 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..5a067da372e --- /dev/null +++ b/apps/desktop/src/vault/app/vault/item-footer.component.html @@ -0,0 +1,63 @@ + 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..d83a530cc40 --- /dev/null +++ b/apps/desktop/src/vault/app/vault/item-footer.component.ts @@ -0,0 +1,156 @@ +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 { 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", + 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.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/password-history.component.ts b/apps/desktop/src/vault/app/vault/password-history.component.ts index 4a87617d8f4..e83ce0d56ea 100644 --- a/apps/desktop/src/vault/app/vault/password-history.component.ts +++ b/apps/desktop/src/vault/app/vault/password-history.component.ts @@ -10,6 +10,7 @@ import { ToastService } from "@bitwarden/components"; @Component({ selector: "app-password-history", templateUrl: "password-history.component.html", + standalone: false, }) export class PasswordHistoryComponent extends BasePasswordHistoryComponent { constructor( diff --git a/apps/desktop/src/vault/app/vault/share.component.ts b/apps/desktop/src/vault/app/vault/share.component.ts index 6926e7e2abf..50842439323 100644 --- a/apps/desktop/src/vault/app/vault/share.component.ts +++ b/apps/desktop/src/vault/app/vault/share.component.ts @@ -13,6 +13,7 @@ import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.servi @Component({ selector: "app-vault-share", templateUrl: "share.component.html", + standalone: false, }) export class ShareComponent extends BaseShareComponent { constructor( diff --git a/apps/desktop/src/vault/app/vault/vault-filter/filters/collection-filter.component.html b/apps/desktop/src/vault/app/vault/vault-filter/filters/collection-filter.component.html index 667cdf6798c..e123f4400c7 100644 --- a/apps/desktop/src/vault/app/vault/vault-filter/filters/collection-filter.component.html +++ b/apps/desktop/src/vault/app/vault/vault-filter/filters/collection-filter.component.html @@ -55,7 +55,7 @@ >  {{ c.node.name }} diff --git a/apps/desktop/src/vault/app/vault/vault-filter/filters/collection-filter.component.ts b/apps/desktop/src/vault/app/vault/vault-filter/filters/collection-filter.component.ts index 161c9ae5353..22372410e5b 100644 --- a/apps/desktop/src/vault/app/vault/vault-filter/filters/collection-filter.component.ts +++ b/apps/desktop/src/vault/app/vault/vault-filter/filters/collection-filter.component.ts @@ -5,5 +5,6 @@ import { CollectionFilterComponent as BaseCollectionFilterComponent } from "@bit @Component({ selector: "app-collection-filter", templateUrl: "collection-filter.component.html", + standalone: false, }) export class CollectionFilterComponent extends BaseCollectionFilterComponent {} diff --git a/apps/desktop/src/vault/app/vault/vault-filter/filters/folder-filter.component.ts b/apps/desktop/src/vault/app/vault/vault-filter/filters/folder-filter.component.ts index 790d31a65e6..d7364808f6d 100644 --- a/apps/desktop/src/vault/app/vault/vault-filter/filters/folder-filter.component.ts +++ b/apps/desktop/src/vault/app/vault/vault-filter/filters/folder-filter.component.ts @@ -5,5 +5,6 @@ import { FolderFilterComponent as BaseFolderFilterComponent } from "@bitwarden/a @Component({ selector: "app-folder-filter", templateUrl: "folder-filter.component.html", + standalone: false, }) export class FolderFilterComponent extends BaseFolderFilterComponent {} diff --git a/apps/desktop/src/vault/app/vault/vault-filter/filters/organization-filter.component.ts b/apps/desktop/src/vault/app/vault/vault-filter/filters/organization-filter.component.ts index 39f1c0200ea..33a47cdc91f 100644 --- a/apps/desktop/src/vault/app/vault/vault-filter/filters/organization-filter.component.ts +++ b/apps/desktop/src/vault/app/vault/vault-filter/filters/organization-filter.component.ts @@ -12,6 +12,7 @@ import { ToastService } from "@bitwarden/components"; @Component({ selector: "app-organization-filter", templateUrl: "organization-filter.component.html", + standalone: false, }) export class OrganizationFilterComponent extends BaseOrganizationFilterComponent { get show() { diff --git a/apps/desktop/src/vault/app/vault/vault-filter/filters/status-filter.component.ts b/apps/desktop/src/vault/app/vault/vault-filter/filters/status-filter.component.ts index 5d43fd52d20..276b11d7138 100644 --- a/apps/desktop/src/vault/app/vault/vault-filter/filters/status-filter.component.ts +++ b/apps/desktop/src/vault/app/vault/vault-filter/filters/status-filter.component.ts @@ -5,5 +5,6 @@ import { StatusFilterComponent as BaseStatusFilterComponent } from "@bitwarden/a @Component({ selector: "app-status-filter", templateUrl: "status-filter.component.html", + standalone: false, }) export class StatusFilterComponent extends BaseStatusFilterComponent {} diff --git a/apps/desktop/src/vault/app/vault/vault-filter/filters/type-filter.component.ts b/apps/desktop/src/vault/app/vault/vault-filter/filters/type-filter.component.ts index 5727cc0e9d5..5920233b206 100644 --- a/apps/desktop/src/vault/app/vault/vault-filter/filters/type-filter.component.ts +++ b/apps/desktop/src/vault/app/vault/vault-filter/filters/type-filter.component.ts @@ -5,6 +5,7 @@ import { TypeFilterComponent as BaseTypeFilterComponent } from "@bitwarden/angul @Component({ selector: "app-type-filter", templateUrl: "type-filter.component.html", + standalone: false, }) export class TypeFilterComponent extends BaseTypeFilterComponent { constructor() { diff --git a/apps/desktop/src/vault/app/vault/vault-filter/vault-filter.component.ts b/apps/desktop/src/vault/app/vault/vault-filter/vault-filter.component.ts index 12ac1fef425..161d22687e8 100644 --- a/apps/desktop/src/vault/app/vault/vault-filter/vault-filter.component.ts +++ b/apps/desktop/src/vault/app/vault/vault-filter/vault-filter.component.ts @@ -5,5 +5,6 @@ import { VaultFilterComponent as BaseVaultFilterComponent } from "@bitwarden/ang @Component({ selector: "app-vault-filter", templateUrl: "vault-filter.component.html", + standalone: false, }) export class VaultFilterComponent extends BaseVaultFilterComponent {} 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 @@ +
+ +
+ +
+ +
+ +
+
+
+ +

{{ "noItemsInList" | 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..5a832ed79b0 --- /dev/null +++ b/apps/desktop/src/vault/app/vault/vault-items-v2.component.ts @@ -0,0 +1,41 @@ +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", + 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-items.component.html b/apps/desktop/src/vault/app/vault/vault-items.component.html index b64bdaa1c80..8a869cd2a32 100644 --- a/apps/desktop/src/vault/app/vault/vault-items.component.html +++ b/apps/desktop/src/vault/app/vault/vault-items.component.html @@ -28,7 +28,7 @@ {{ c.name }} diff --git a/apps/desktop/src/vault/app/vault/vault-items.component.ts b/apps/desktop/src/vault/app/vault/vault-items.component.ts index d5838459ff7..2d1ba784753 100644 --- a/apps/desktop/src/vault/app/vault/vault-items.component.ts +++ b/apps/desktop/src/vault/app/vault/vault-items.component.ts @@ -14,6 +14,7 @@ import { SearchBarService } from "../../../app/layout/search/search-bar.service" @Component({ selector: "app-vault-items", templateUrl: "vault-items.component.html", + standalone: false, }) export class VaultItemsComponent extends BaseVaultItemsComponent { constructor( 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..4dd23466126 --- /dev/null +++ b/apps/desktop/src/vault/app/vault/vault-v2.component.html @@ -0,0 +1,81 @@ +
+ + +
+ +
+
+
+ + + + + + + +
+
+
+
+ +
+ + +
+
+ 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..50e6bfb51c7 --- /dev/null +++ b/apps/desktop/src/vault/app/vault/vault-v2.component.ts @@ -0,0 +1,785 @@ +import { CommonModule } from "@angular/common"; +import { + ChangeDetectorRef, + Component, + NgZone, + OnDestroy, + OnInit, + ViewChild, + ViewContainerRef, +} from "@angular/core"; +import { ActivatedRoute, Router } from "@angular/router"; +import { firstValueFrom, Subject, takeUntil, switchMap, lastValueFrom } from "rxjs"; +import { filter, map, take } from "rxjs/operators"; + +import { CollectionService, 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, CollectionId, OrganizationId, 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 { 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, toCipherType } 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 { + BadgeModule, + ButtonModule, + DialogService, + ItemModule, + ToastService, +} from "@bitwarden/components"; +import { I18nPipe } from "@bitwarden/ui-common"; +import { + AddEditFolderDialogComponent, + AddEditFolderDialogResult, + 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 { 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", + 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; + isSubmitting = false; + + 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, + private collectionService: CollectionService, + private folderService: FolderService, + ) {} + + 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(); + const paramCipherAddType = toCipherType(params.addType); + 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" && paramCipherAddType) { + this.addType = paramCipherAddType; + await this.addCipher(this.addType).catch(() => {}); + } + + const paramCipherType = toCipherType(params.type); + this.activeFilter = new VaultFilter({ + status: params.deleted ? "trash" : params.favorites ? "favorites" : "all", + cipherType: params.action === "add" || paramCipherType == null ? undefined : paramCipherType, + 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"); + if (!cipher.edit && this.config) { + this.config.mode = "partial-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) { + if (this.action === "add") { + return; + } + this.addType = type || this.activeFilter.cipherType; + this.cipher = new CipherView(); + 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.collections = await firstValueFrom( + this.collectionService.decryptedCollectionViews$(cipher.collectionIds as CollectionId[]), + ); + 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(() => {}); + this.isSubmitting = false; + } + + 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) { + const folderView = await firstValueFrom( + this.folderService.getDecrypted$(folderId, this.activeUserId), + ); + + const dialogRef = AddEditFolderDialogComponent.open(this.dialogService, { + editFolderConfig: { + folder: { + ...folderView, + }, + }, + }); + + const result = await lastValueFrom(dialogRef.closed); + + if ( + result === AddEditFolderDialogResult.Deleted || + result === AddEditFolderDialogResult.Created + ) { + await this.vaultFilterComponent.reloadCollectionsAndFolders(this.activeFilter); + } + } + + 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(); + }); + } + + protected onSubmit = async () => { + this.isSubmitting = true; + return Promise.resolve(true); + }; + + 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; + } + + if (this.addOrganizationId && this.config) { + this.config.initialValues = { + ...this.config.initialValues, + organizationId: this.addOrganizationId as OrganizationId, + }; + } + } + + 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.html b/apps/desktop/src/vault/app/vault/vault.component.html index 99131a848cc..9a25619b1a8 100644 --- a/apps/desktop/src/vault/app/vault/vault.component.html +++ b/apps/desktop/src/vault/app/vault/vault.component.html @@ -15,6 +15,7 @@ *ngIf="cipherId && action === 'view'" [cipherId]="cipherId" [collectionId]="activeFilter?.selectedCollectionId" + [masterPasswordAlreadyPrompted]="cipherRepromptId === cipherId" (onCloneCipher)="cloneCipherWithoutPasswordPrompt($event)" (onEditCipher)="editCipher($event)" (onViewCipherPasswordHistory)="viewCipherPasswordHistory($event)" diff --git a/apps/desktop/src/vault/app/vault/vault.component.ts b/apps/desktop/src/vault/app/vault/vault.component.ts index a21a285a428..0d66dbc7d72 100644 --- a/apps/desktop/src/vault/app/vault/vault.component.ts +++ b/apps/desktop/src/vault/app/vault/vault.component.ts @@ -10,7 +10,7 @@ import { ViewContainerRef, } from "@angular/core"; import { ActivatedRoute, Router } from "@angular/router"; -import { firstValueFrom, Subject, takeUntil, switchMap } from "rxjs"; +import { firstValueFrom, Subject, takeUntil, switchMap, lastValueFrom } from "rxjs"; import { filter, first, map, take } from "rxjs/operators"; import { ModalRef } from "@bitwarden/angular/components/modal/modal.ref"; @@ -23,20 +23,24 @@ 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 { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { MessagingService } from "@bitwarden/common/platform/abstractions/messaging.service"; import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; import { SyncService } from "@bitwarden/common/platform/sync"; import { CipherId, 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 { TotpService } from "@bitwarden/common/vault/abstractions/totp.service"; -import { CipherType } from "@bitwarden/common/vault/enums"; +import { CipherType, toCipherType } 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 { DialogService, ToastService } from "@bitwarden/components"; -import { DecryptionFailureDialogComponent, PasswordRepromptService } from "@bitwarden/vault"; +import { + AddEditFolderDialogComponent, + AddEditFolderDialogResult, + DecryptionFailureDialogComponent, + PasswordRepromptService, +} from "@bitwarden/vault"; import { SearchBarService } from "../../../app/layout/search/search-bar.service"; import { invokeMenu, RendererMenuItem } from "../../../utils"; @@ -45,7 +49,6 @@ import { AddEditComponent } from "./add-edit.component"; import { AttachmentsComponent } from "./attachments.component"; import { CollectionsComponent } from "./collections.component"; import { CredentialGeneratorDialogComponent } from "./credential-generator-dialog.component"; -import { FolderAddEditComponent } from "./folder-add-edit.component"; import { PasswordHistoryComponent } from "./password-history.component"; import { ShareComponent } from "./share.component"; import { VaultFilterComponent } from "./vault-filter/vault-filter.component"; @@ -57,6 +60,7 @@ const BroadcasterSubscriptionId = "VaultComponent"; @Component({ selector: "app-vault", templateUrl: "vault.component.html", + standalone: false, }) export class VaultComponent implements OnInit, OnDestroy { @ViewChild(ViewComponent) viewComponent: ViewComponent; @@ -72,8 +76,6 @@ export class VaultComponent implements OnInit, OnDestroy { @ViewChild("share", { read: ViewContainerRef, static: true }) shareModalRef: ViewContainerRef; @ViewChild("collections", { read: ViewContainerRef, static: true }) collectionsModalRef: ViewContainerRef; - @ViewChild("folderAddEdit", { read: ViewContainerRef, static: true }) - folderAddEditModalRef: ViewContainerRef; action: string; cipherId: string = null; @@ -115,9 +117,9 @@ export class VaultComponent implements OnInit, OnDestroy { private dialogService: DialogService, private billingAccountProfileStateService: BillingAccountProfileStateService, private toastService: ToastService, - private configService: ConfigService, private accountService: AccountService, private cipherService: CipherService, + private folderService: FolderService, ) {} async ngOnInit() { @@ -151,6 +153,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; @@ -277,16 +282,16 @@ export class VaultComponent implements OnInit, OnDestroy { await this.viewCipher(cipherView); } } else if (params.action === "add") { - this.addType = Number(params.addType); + this.addType = toCipherType(params.addType); // FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling. // eslint-disable-next-line @typescript-eslint/no-floating-promises this.addCipher(this.addType); } + const paramCipherType = toCipherType(params.type); this.activeFilter = new VaultFilter({ status: params.deleted ? "trash" : params.favorites ? "favorites" : "all", - cipherType: - params.action === "add" || params.type == null ? null : parseInt(params.type, null), + cipherType: params.action === "add" || paramCipherType == null ? null : paramCipherType, selectedFolderId: params.folderId, selectedCollectionId: params.selectedCollectionId, selectedOrganizationId: params.selectedOrganizationId, @@ -470,6 +475,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() { @@ -694,32 +707,26 @@ export class VaultComponent implements OnInit, OnDestroy { } async editFolder(folderId: string) { - if (this.modal != null) { - this.modal.close(); - } - - const [modal, childComponent] = await this.modalService.openViewRef( - FolderAddEditComponent, - this.folderAddEditModalRef, - (comp) => (comp.folderId = folderId), + const folderView = await firstValueFrom( + this.folderService.getDecrypted$(folderId, this.activeUserId), ); - this.modal = modal; - // eslint-disable-next-line rxjs-angular/prefer-takeuntil, rxjs/no-async-subscribe - childComponent.onSavedFolder.subscribe(async (folder: FolderView) => { - this.modal.close(); - await this.vaultFilterComponent.reloadCollectionsAndFolders(this.activeFilter); - }); - // eslint-disable-next-line rxjs-angular/prefer-takeuntil, rxjs/no-async-subscribe - childComponent.onDeletedFolder.subscribe(async (folder: FolderView) => { - this.modal.close(); - await this.vaultFilterComponent.reloadCollectionsAndFolders(this.activeFilter); + const dialogRef = AddEditFolderDialogComponent.open(this.dialogService, { + editFolderConfig: { + folder: { + ...folderView, + }, + }, }); - // eslint-disable-next-line rxjs-angular/prefer-takeuntil - this.modal.onClosed.subscribe(() => { - this.modal = null; - }); + const result = await lastValueFrom(dialogRef.closed); + + if ( + result === AddEditFolderDialogResult.Deleted || + result === AddEditFolderDialogResult.Created + ) { + await this.vaultFilterComponent.reloadCollectionsAndFolders(this.activeFilter); + } } private dirtyInput(): boolean { diff --git a/apps/desktop/src/vault/app/vault/view-custom-fields.component.ts b/apps/desktop/src/vault/app/vault/view-custom-fields.component.ts index 249f83c4444..efe61ad1fa7 100644 --- a/apps/desktop/src/vault/app/vault/view-custom-fields.component.ts +++ b/apps/desktop/src/vault/app/vault/view-custom-fields.component.ts @@ -6,6 +6,7 @@ import { EventCollectionService } from "@bitwarden/common/abstractions/event/eve @Component({ selector: "app-vault-view-custom-fields", templateUrl: "view-custom-fields.component.html", + standalone: false, }) export class ViewCustomFieldsComponent extends BaseViewCustomFieldsComponent { constructor(eventCollectionService: EventCollectionService) { diff --git a/apps/desktop/src/vault/app/vault/view.component.html b/apps/desktop/src/vault/app/vault/view.component.html index 8477a588fef..d3e3a751d9d 100644 --- a/apps/desktop/src/vault/app/vault/view.component.html +++ b/apps/desktop/src/vault/app/vault/view.component.html @@ -656,11 +656,7 @@ class="primary" (click)="restore()" appA11yTitle="{{ 'restore' | i18n }}" - *ngIf=" - (limitItemDeletion$ | async) - ? (canRestoreCipher$ | async) && cipher.isDeleted - : cipher.isDeleted - " + *ngIf="(canRestoreCipher$ | async) && cipher.isDeleted" > diff --git a/apps/desktop/src/vault/app/vault/view.component.ts b/apps/desktop/src/vault/app/vault/view.component.ts index e5f677cbca6..7e7f7b57fc8 100644 --- a/apps/desktop/src/vault/app/vault/view.component.ts +++ b/apps/desktop/src/vault/app/vault/view.component.ts @@ -3,11 +3,13 @@ import { ChangeDetectorRef, Component, EventEmitter, + Input, NgZone, OnChanges, OnDestroy, OnInit, Output, + SimpleChanges, } from "@angular/core"; import { ViewComponent as BaseViewComponent } from "@bitwarden/angular/vault/components/view.component"; @@ -17,7 +19,6 @@ import { EventCollectionService } from "@bitwarden/common/abstractions/event/eve import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; import { TokenService } from "@bitwarden/common/auth/abstractions/token.service"; import { BillingAccountProfileStateService } from "@bitwarden/common/billing/abstractions/account/billing-account-profile-state.service"; -import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum"; import { EncryptService } from "@bitwarden/common/key-management/crypto/abstractions/encrypt.service"; import { BroadcasterService } from "@bitwarden/common/platform/abstractions/broadcaster.service"; import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; @@ -42,9 +43,11 @@ const BroadcasterSubscriptionId = "ViewComponent"; @Component({ selector: "app-vault-view", templateUrl: "view.component.html", + standalone: false, }) export class ViewComponent extends BaseViewComponent implements OnInit, OnDestroy, OnChanges { @Output() onViewCipherPasswordHistory = new EventEmitter(); + @Input() masterPasswordAlreadyPrompted: boolean = false; constructor( cipherService: CipherService, @@ -72,7 +75,7 @@ export class ViewComponent extends BaseViewComponent implements OnInit, OnDestro accountService: AccountService, toastService: ToastService, cipherAuthorizationService: CipherAuthorizationService, - private configService: ConfigService, + configService: ConfigService, ) { super( cipherService, @@ -100,11 +103,10 @@ export class ViewComponent extends BaseViewComponent implements OnInit, OnDestro billingAccountProfileStateService, toastService, cipherAuthorizationService, + configService, ); } - protected limitItemDeletion$ = this.configService.getFeatureFlag$(FeatureFlag.LimitItemDeletion); - ngOnInit() { super.ngOnInit(); @@ -118,6 +120,7 @@ export class ViewComponent extends BaseViewComponent implements OnInit, OnDestro } }); }); + this.passwordReprompted = this.masterPasswordAlreadyPrompted; } ngOnDestroy() { @@ -125,13 +128,20 @@ export class ViewComponent extends BaseViewComponent implements OnInit, OnDestro this.broadcasterService.unsubscribe(BroadcasterSubscriptionId); } - async ngOnChanges() { + async ngOnChanges(changes: SimpleChanges) { if (this.cipher?.decryptionFailure) { DecryptionFailureDialogComponent.open(this.dialogService, { cipherIds: [this.cipherId as CipherId], }); return; } + this.passwordReprompted = this.masterPasswordAlreadyPrompted; + + if (changes["cipherId"]) { + if (changes["cipherId"].currentValue !== changes["cipherId"].previousValue) { + this.showPrivateKey = false; + } + } } viewHistory() { 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/desktop/tsconfig.json b/apps/desktop/tsconfig.json index 78b3512405e..7db3e84e451 100644 --- a/apps/desktop/tsconfig.json +++ b/apps/desktop/tsconfig.json @@ -1,50 +1,5 @@ { - "compilerOptions": { - "moduleResolution": "node", - "noImplicitAny": true, - "emitDecoratorMetadata": true, - "experimentalDecorators": true, - "module": "ES2020", - "target": "ES2016", - "sourceMap": true, - "types": [], - "baseUrl": ".", - "paths": { - "@bitwarden/admin-console/common": ["../../libs/admin-console/src/common"], - "@bitwarden/angular/*": ["../../libs/angular/src/*"], - "@bitwarden/auth/common": ["../../libs/auth/src/common"], - "@bitwarden/auth/angular": ["../../libs/auth/src/angular"], - "@bitwarden/billing": ["../../libs/billing/src"], - "@bitwarden/common/*": ["../../libs/common/src/*"], - "@bitwarden/components": ["../../libs/components/src"], - "@bitwarden/generator-components": ["../../libs/tools/generator/components/src"], - "@bitwarden/generator-core": ["../../libs/tools/generator/core/src"], - "@bitwarden/generator-history": ["../../libs/tools/generator/extensions/history/src"], - "@bitwarden/generator-legacy": ["../../libs/tools/generator/extensions/legacy/src"], - "@bitwarden/generator-navigation": ["../../libs/tools/generator/extensions/navigation/src"], - "@bitwarden/importer-core": ["../../libs/importer/src"], - "@bitwarden/importer-ui": ["../../libs/importer/src/components"], - "@bitwarden/key-management": ["../../libs/key-management/src"], - "@bitwarden/key-management-ui": ["../../libs/key-management-ui/src"], - "@bitwarden/node/*": ["../../libs/node/src/*"], - "@bitwarden/platform": ["../../libs/platform/src"], - "@bitwarden/send-ui": ["../../libs/tools/send/send-ui/src"], - "@bitwarden/tools-card": ["../../libs/tools/card/src"], - "@bitwarden/ui-common": ["../../libs/ui/common/src"], - "@bitwarden/ui-common/setup-jest": ["../../libs/ui/common/src/setup-jest"], - "@bitwarden/vault-export-core": [ - "../../libs/tools/export/vault-export/vault-export-core/src" - ], - "@bitwarden/vault-export-ui": ["../../libs/tools/export/vault-export/vault-export-ui/src"], - "@bitwarden/vault": ["../../libs/vault/src"] - }, - "plugins": [ - { - "name": "typescript-strict-plugin" - } - ], - "useDefineForClassFields": false - }, + "extends": "../../tsconfig.base", "angularCompilerOptions": { "strictTemplates": true }, diff --git a/apps/desktop/tsconfig.spec.json b/apps/desktop/tsconfig.spec.json index de184bd7608..d52d889aa78 100644 --- a/apps/desktop/tsconfig.spec.json +++ b/apps/desktop/tsconfig.spec.json @@ -1,4 +1,8 @@ { "extends": "./tsconfig.json", + "compilerOptions": { + "isolatedModules": true, + "emitDecoratorMetadata": false + }, "files": ["./test.setup.ts"] } 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/).

- - Github Workflow build on master + + Github Workflow build on main Crowdin diff --git a/apps/web/entrypoint.sh b/apps/web/entrypoint.sh index 16d1c78fb77..53e8af235fb 100644 --- a/apps/web/entrypoint.sh +++ b/apps/web/entrypoint.sh @@ -19,20 +19,29 @@ then LGID=65534 fi -# Create user and group +if [ "$(id -u)" = "0" ]; then + # Create user and group -groupadd -o -g $LGID $GROUPNAME >/dev/null 2>&1 || -groupmod -o -g $LGID $GROUPNAME >/dev/null 2>&1 -useradd -o -u $LUID -g $GROUPNAME -s /bin/false $USERNAME >/dev/null 2>&1 || -usermod -o -u $LUID -g $GROUPNAME -s /bin/false $USERNAME >/dev/null 2>&1 -mkhomedir_helper $USERNAME + groupadd -o -g $LGID $GROUPNAME >/dev/null 2>&1 || + groupmod -o -g $LGID $GROUPNAME >/dev/null 2>&1 + useradd -o -u $LUID -g $GROUPNAME -s /bin/false $USERNAME >/dev/null 2>&1 || + usermod -o -u $LUID -g $GROUPNAME -s /bin/false $USERNAME >/dev/null 2>&1 + mkhomedir_helper $USERNAME -# The rest... + # The rest... -chown -R $USERNAME:$GROUPNAME /etc/bitwarden -cp /etc/bitwarden/web/app-id.json /app/app-id.json -chown -R $USERNAME:$GROUPNAME /app -chown -R $USERNAME:$GROUPNAME /bitwarden_server + chown -R $USERNAME:$GROUPNAME /etc/bitwarden + chown -R $USERNAME:$GROUPNAME /app + chown -R $USERNAME:$GROUPNAME /bitwarden_server -exec gosu $USERNAME:$GROUPNAME dotnet /bitwarden_server/Server.dll \ - /contentRoot=/app /webRoot=. /serveUnknown=false /webVault=true + gosu_cmd="gosu $USERNAME:$GROUPNAME" +else + gosu_cmd="" +fi + +exec $gosu_cmd /bitwarden_server/Server \ + /contentRoot=/app \ + /webRoot=. \ + /serveUnknown=false \ + /webVault=true \ + /appIdLocation=/etc/bitwarden/web/app-id.json diff --git a/apps/web/jest.config.js b/apps/web/jest.config.js index 9b5d6fdc766..e1d64fa64c3 100644 --- a/apps/web/jest.config.js +++ b/apps/web/jest.config.js @@ -1,13 +1,12 @@ const { pathsToModuleNameMapper } = require("ts-jest"); -const { compilerOptions } = require("./tsconfig"); +const { compilerOptions } = require("../../tsconfig.base"); const sharedConfig = require("../../libs/shared/jest.config.angular"); /** @type {import('jest').Config} */ module.exports = { ...sharedConfig, - preset: "jest-preset-angular", setupFilesAfterEnv: ["/test.setup.ts"], moduleNameMapper: { // Replace ESM SDK with Node compatible SDK @@ -16,11 +15,11 @@ module.exports = { ...pathsToModuleNameMapper( { // lets us use @bitwarden/common/spec in web tests - "@bitwarden/common/spec": ["../../libs/common/spec"], + "@bitwarden/common/spec": ["libs/common/spec"], ...(compilerOptions?.paths ?? {}), }, { - prefix: "/", + prefix: "/../../", }, ), }, diff --git a/apps/web/package.json b/apps/web/package.json index 1f4ae9c29cf..cbeb012169a 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.6.0", "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/common/base-members.component.ts b/apps/web/src/app/admin-console/common/base-members.component.ts index 1ecf122e429..488af7ee518 100644 --- a/apps/web/src/app/admin-console/common/base-members.component.ts +++ b/apps/web/src/app/admin-console/common/base-members.component.ts @@ -50,11 +50,15 @@ export abstract class BaseMembersComponent { } get showBulkConfirmUsers(): boolean { - return this.dataSource.acceptedUserCount > 0; + return this.dataSource + .getCheckedUsers() + .every((member) => member.status == this.userStatusType.Accepted); } get showBulkReinviteUsers(): boolean { - return this.dataSource.invitedUserCount > 0; + return this.dataSource + .getCheckedUsers() + .every((member) => member.status == this.userStatusType.Invited); } abstract userType: typeof OrganizationUserType | typeof ProviderUserType; diff --git a/apps/web/src/app/admin-console/organizations/collections/bulk-collections-dialog/bulk-collections-dialog.component.ts b/apps/web/src/app/admin-console/organizations/collections/bulk-collections-dialog/bulk-collections-dialog.component.ts index dd19c66f21e..7c4e2156ffb 100644 --- a/apps/web/src/app/admin-console/organizations/collections/bulk-collections-dialog/bulk-collections-dialog.component.ts +++ b/apps/web/src/app/admin-console/organizations/collections/bulk-collections-dialog/bulk-collections-dialog.component.ts @@ -43,6 +43,8 @@ export interface BulkCollectionsDialogParams { collections: CollectionView[]; } +// FIXME: update to use a const object instead of a typescript enum +// eslint-disable-next-line @bitwarden/platform/no-enums export enum BulkCollectionsDialogResult { Saved = "saved", Canceled = "canceled", @@ -52,7 +54,6 @@ export enum BulkCollectionsDialogResult { imports: [SharedModule, AccessSelectorModule], selector: "app-bulk-collections-dialog", templateUrl: "bulk-collections-dialog.component.html", - standalone: true, }) export class BulkCollectionsDialogComponent implements OnDestroy { protected readonly PermissionMode = PermissionMode; diff --git a/apps/web/src/app/admin-console/organizations/collections/collection-access-restricted.component.ts b/apps/web/src/app/admin-console/organizations/collections/collection-access-restricted.component.ts index 15ba10a0d59..3f26e03e203 100644 --- a/apps/web/src/app/admin-console/organizations/collections/collection-access-restricted.component.ts +++ b/apps/web/src/app/admin-console/organizations/collections/collection-access-restricted.component.ts @@ -12,7 +12,6 @@ const icon = svgIcon` {{ "youDoNotHavePermissions" | i18n }} diff --git a/apps/web/src/app/admin-console/organizations/collections/collection-badge/collection-name.badge.component.ts b/apps/web/src/app/admin-console/organizations/collections/collection-badge/collection-name.badge.component.ts index d8ace8acc56..728faaf66e2 100644 --- a/apps/web/src/app/admin-console/organizations/collections/collection-badge/collection-name.badge.component.ts +++ b/apps/web/src/app/admin-console/organizations/collections/collection-badge/collection-name.badge.component.ts @@ -10,7 +10,6 @@ import { GetCollectionNameFromIdPipe } from "../pipes"; @Component({ selector: "app-collection-badge", templateUrl: "collection-name-badge.component.html", - standalone: true, imports: [SharedModule, GetCollectionNameFromIdPipe], }) export class CollectionNameBadgeComponent { diff --git a/apps/web/src/app/admin-console/organizations/collections/group-badge/group-name-badge.component.ts b/apps/web/src/app/admin-console/organizations/collections/group-badge/group-name-badge.component.ts index 8e5f261bc26..8f703acf9af 100644 --- a/apps/web/src/app/admin-console/organizations/collections/group-badge/group-name-badge.component.ts +++ b/apps/web/src/app/admin-console/organizations/collections/group-badge/group-name-badge.component.ts @@ -10,6 +10,7 @@ import { GroupView } from "../../core"; @Component({ selector: "app-group-badge", templateUrl: "group-name-badge.component.html", + standalone: false, }) export class GroupNameBadgeComponent implements OnChanges { @Input() selectedGroups: SelectionReadOnlyRequest[]; diff --git a/apps/web/src/app/admin-console/organizations/collections/pipes/get-collection-name.pipe.ts b/apps/web/src/app/admin-console/organizations/collections/pipes/get-collection-name.pipe.ts index 8833ddfa382..b52719304b8 100644 --- a/apps/web/src/app/admin-console/organizations/collections/pipes/get-collection-name.pipe.ts +++ b/apps/web/src/app/admin-console/organizations/collections/pipes/get-collection-name.pipe.ts @@ -5,7 +5,6 @@ import { CollectionView } from "@bitwarden/admin-console/common"; @Pipe({ name: "collectionNameFromId", pure: true, - standalone: true, }) export class GetCollectionNameFromIdPipe implements PipeTransform { transform(value: string, collections: CollectionView[]) { 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..f19c3f64530 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,52 @@ export function getNestedCollectionTree( return nodes; } +export function getNestedCollectionTree_vNext( + collections: (CollectionView | CollectionAdminView)[], +): TreeNode[] { + if (!collections) { + return []; + } + + // Collections need to be cloned because ServiceUtils.nestedTraverse actively + // modifies the names of collections. + // These changes risk affecting collections store in StateService. + const clonedCollections = collections + .sort((a, b) => a.name.localeCompare(b.name)) + .map(cloneCollection); + + const nodes: TreeNode[] = []; + clonedCollections.forEach((collection) => { + const parts = + collection.name != null + ? collection.name.replace(/^\/+|\/+$/g, "").split(NestingDelimiter) + : []; + ServiceUtils.nestedTraverse_vNext(nodes, 0, parts, collection, null, NestingDelimiter); + }); + 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-filter/vault-filter.component.ts b/apps/web/src/app/admin-console/organizations/collections/vault-filter/vault-filter.component.ts index 384390d738e..ff6ec9af0af 100644 --- a/apps/web/src/app/admin-console/organizations/collections/vault-filter/vault-filter.component.ts +++ b/apps/web/src/app/admin-console/organizations/collections/vault-filter/vault-filter.component.ts @@ -12,6 +12,7 @@ import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.servic import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; import { TreeNode } from "@bitwarden/common/vault/models/domain/tree-node"; import { DialogService, ToastService } from "@bitwarden/components"; +import { RestrictedItemTypesService } from "@bitwarden/vault"; import { VaultFilterComponent as BaseVaultFilterComponent } from "../../../../vault/individual-vault/vault-filter/components/vault-filter.component"; import { VaultFilterService } from "../../../../vault/individual-vault/vault-filter/services/abstractions/vault-filter.service"; @@ -26,6 +27,7 @@ import { CollectionFilter } from "../../../../vault/individual-vault/vault-filte selector: "app-organization-vault-filter", templateUrl: "../../../../vault/individual-vault/vault-filter/components/vault-filter.component.html", + standalone: false, }) export class VaultFilterComponent extends BaseVaultFilterComponent @@ -50,6 +52,7 @@ export class VaultFilterComponent protected dialogService: DialogService, protected configService: ConfigService, protected accountService: AccountService, + protected restrictedItemTypesService: RestrictedItemTypesService, ) { super( vaultFilterService, @@ -61,6 +64,7 @@ export class VaultFilterComponent dialogService, configService, accountService, + restrictedItemTypesService, ); } @@ -105,14 +109,14 @@ export class VaultFilterComponent id: "AllCollections", name: "collections", type: "all", - icon: "bwi-collection", + icon: "bwi-collection-shared", }, [ { id: "AllCollections", name: "Collections", type: "all", - icon: "bwi-collection", + icon: "bwi-collection-shared", }, ], ), diff --git a/apps/web/src/app/admin-console/organizations/collections/vault-header/vault-header.component.html b/apps/web/src/app/admin-console/organizations/collections/vault-header/vault-header.component.html index c2e80b7524f..60214a9fd61 100644 --- a/apps/web/src/app/admin-console/organizations/collections/vault-header/vault-header.component.html +++ b/apps/web/src/app/admin-console/organizations/collections/vault-header/vault-header.component.html @@ -14,7 +14,7 @@ @@ -115,7 +114,7 @@ id="newItemDropdown" appA11yTitle="{{ 'new' | i18n }}" > - + {{ "new" | i18n }} @@ -140,7 +139,7 @@ diff --git a/apps/web/src/app/admin-console/organizations/collections/vault-header/vault-header.component.ts b/apps/web/src/app/admin-console/organizations/collections/vault-header/vault-header.component.ts index 7efb79ebdb6..b343d5874bc 100644 --- a/apps/web/src/app/admin-console/organizations/collections/vault-header/vault-header.component.ts +++ b/apps/web/src/app/admin-console/organizations/collections/vault-header/vault-header.component.ts @@ -35,7 +35,6 @@ import { import { CollectionDialogTabType } from "../../shared/components/collection-dialog"; @Component({ - standalone: true, selector: "app-org-vault-header", templateUrl: "./vault-header.component.html", imports: [ @@ -117,7 +116,7 @@ export class VaultHeaderComponent { } get icon() { - return this.filter.collectionId !== undefined ? "bwi-collection" : ""; + return this.filter.collectionId !== undefined ? "bwi-collection-shared" : ""; } protected get showBreadcrumbs() { 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..e8782ca0f2d 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 @@ - + + + + + - + - - - diff --git a/apps/web/src/app/admin-console/organizations/collections/vault.component.ts b/apps/web/src/app/admin-console/organizations/collections/vault.component.ts index 97193bf1b1f..bc0f517d1fb 100644 --- a/apps/web/src/app/admin-console/organizations/collections/vault.component.ts +++ b/apps/web/src/app/admin-console/organizations/collections/vault.component.ts @@ -13,6 +13,7 @@ import { Subject, } from "rxjs"; import { + catchError, concatMap, debounceTime, distinctUntilChanged, @@ -23,7 +24,6 @@ import { switchMap, takeUntil, tap, - catchError, } from "rxjs/operators"; import { @@ -44,6 +44,7 @@ import { getUserId } from "@bitwarden/common/auth/services/account.service"; import { OrganizationBillingServiceAbstraction } from "@bitwarden/common/billing/abstractions"; import { BillingApiServiceAbstraction } from "@bitwarden/common/billing/abstractions/billing-api.service.abstraction"; import { EventType } from "@bitwarden/common/enums"; +import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum"; import { BroadcasterService } from "@bitwarden/common/platform/abstractions/broadcaster.service"; import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; @@ -61,20 +62,24 @@ import { TreeNode } from "@bitwarden/common/vault/models/domain/tree-node"; import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view"; import { ServiceUtils } from "@bitwarden/common/vault/service-utils"; import { - DialogRef, BannerModule, + DialogRef, DialogService, Icons, NoItemsModule, ToastService, } from "@bitwarden/components"; import { + AttachmentDialogResult, + AttachmentsV2Component, CipherFormConfig, CipherFormConfigService, CollectionAssignmentResult, DecryptionFailureDialogComponent, PasswordRepromptService, } from "@bitwarden/vault"; +import { OrganizationWarningsService } from "@bitwarden/web-vault/app/billing/services/organization-warnings.service"; +import { ResellerRenewalWarningComponent } from "@bitwarden/web-vault/app/billing/warnings/reseller-renewal-warning.component"; import { BillingNotificationService } from "../../../billing/services/billing-notification.service"; import { @@ -83,6 +88,7 @@ import { } from "../../../billing/services/reseller-warning.service"; import { TrialFlowService } from "../../../billing/services/trial-flow.service"; import { FreeTrial } from "../../../billing/types/free-trial"; +import { FreeTrialWarningComponent } from "../../../billing/warnings/free-trial-warning.component"; import { SharedModule } from "../../../shared"; import { AssignCollectionsWebComponent } from "../../../vault/components/assign-collections"; import { @@ -92,10 +98,6 @@ import { } from "../../../vault/components/vault-item-dialog/vault-item-dialog.component"; import { VaultItemEvent } from "../../../vault/components/vault-items/vault-item-event"; import { VaultItemsModule } from "../../../vault/components/vault-items/vault-items.module"; -import { - AttachmentDialogResult, - AttachmentsV2Component, -} from "../../../vault/individual-vault/attachments-v2.component"; import { BulkDeleteDialogResult, openBulkDeleteDialog, @@ -123,20 +125,25 @@ import { BulkCollectionsDialogResult, } from "./bulk-collections-dialog"; import { CollectionAccessRestrictedComponent } from "./collection-access-restricted.component"; -import { getNestedCollectionTree } from "./utils"; +import { + getNestedCollectionTree, + getFlatCollectionTree, + getNestedCollectionTree_vNext, +} from "./utils"; import { VaultFilterModule } from "./vault-filter/vault-filter.module"; import { VaultHeaderComponent } from "./vault-header/vault-header.component"; const BroadcasterSubscriptionId = "OrgVaultComponent"; const SearchTextDebounceInterval = 200; +// FIXME: update to use a const object instead of a typescript enum +// eslint-disable-next-line @bitwarden/platform/no-enums enum AddAccessStatusType { All = 0, AddAccess = 1, } @Component({ - standalone: true, selector: "app-org-vault", templateUrl: "vault.component.html", imports: [ @@ -147,6 +154,8 @@ enum AddAccessStatusType { SharedModule, BannerModule, NoItemsModule, + FreeTrialWarningComponent, + ResellerRenewalWarningComponent, ], providers: [ RoutedVaultFilterService, @@ -176,8 +185,9 @@ export class VaultComponent implements OnInit, OnDestroy { protected showCollectionAccessRestricted: boolean; private hasSubscription$ = new BehaviorSubject(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 +267,7 @@ export class VaultComponent implements OnInit, OnDestroy { private resellerWarningService: ResellerWarningService, private accountService: AccountService, private billingNotificationService: BillingNotificationService, + private organizationWarningsService: OrganizationWarningsService, ) {} async ngOnInit() { @@ -354,8 +365,7 @@ export class VaultComponent implements OnInit, OnDestroy { if (this.organization.canEditAllCiphers) { return collections; } - // The user is only allowed to add/edit items to assigned collections that are not readonly - return collections.filter((c) => c.assigned && !c.readOnly); + return collections.filter((c) => c.assigned); }), shareReplay({ refCount: true, bufferSize: 1 }), ); @@ -413,9 +423,16 @@ export class VaultComponent implements OnInit, OnDestroy { }), ); - const nestedCollections$ = allCollections$.pipe( - map((collections) => getNestedCollectionTree(collections)), - shareReplay({ refCount: true, bufferSize: 1 }), + const nestedCollections$ = combineLatest([ + allCollections$, + this.configService.getFeatureFlag$(FeatureFlag.OptimizeNestedTraverseTypescript), + ]).pipe( + map( + ([collections, shouldOptimize]) => + (shouldOptimize + ? getNestedCollectionTree_vNext(collections) + : getNestedCollectionTree(collections)) as TreeNode[], + ), ); const collections$ = combineLatest([ @@ -434,23 +451,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 +647,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 +688,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 +703,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 +863,8 @@ export class VaultComponent implements OnInit, OnDestroy { const dialogRef = AttachmentsV2Component.open(this.dialogService, { cipherId: cipher.id as CipherId, + organizationId: cipher.organizationId as OrganizationId, + admin: true, }); const result = await firstValueFrom(dialogRef.closed); diff --git a/apps/web/src/app/admin-console/organizations/create/organization-information.component.ts b/apps/web/src/app/admin-console/organizations/create/organization-information.component.ts index fc168f842dc..cd14b73a156 100644 --- a/apps/web/src/app/admin-console/organizations/create/organization-information.component.ts +++ b/apps/web/src/app/admin-console/organizations/create/organization-information.component.ts @@ -9,6 +9,7 @@ import { AccountService } from "@bitwarden/common/auth/abstractions/account.serv @Component({ selector: "app-org-info", templateUrl: "organization-information.component.html", + standalone: false, }) export class OrganizationInformationComponent implements OnInit { @Input() nameOnly = false; diff --git a/apps/web/src/app/admin-console/organizations/guards/is-enterprise-org.guard.spec.ts b/apps/web/src/app/admin-console/organizations/guards/is-enterprise-org.guard.spec.ts index f5fce0e5e42..bc4a942301a 100644 --- a/apps/web/src/app/admin-console/organizations/guards/is-enterprise-org.guard.spec.ts +++ b/apps/web/src/app/admin-console/organizations/guards/is-enterprise-org.guard.spec.ts @@ -21,16 +21,19 @@ import { isEnterpriseOrgGuard } from "./is-enterprise-org.guard"; @Component({ template: "

This is the home screen!

", + standalone: false, }) export class HomescreenComponent {} @Component({ template: "

This component can only be accessed by a enterprise organization!

", + standalone: false, }) export class IsEnterpriseOrganizationComponent {} @Component({ template: "

This is the organization upgrade screen!

", + standalone: false, }) export class OrganizationUpgradeScreenComponent {} diff --git a/apps/web/src/app/admin-console/organizations/guards/is-paid-org.guard.spec.ts b/apps/web/src/app/admin-console/organizations/guards/is-paid-org.guard.spec.ts index 8efed8cefa2..ab5fd79321a 100644 --- a/apps/web/src/app/admin-console/organizations/guards/is-paid-org.guard.spec.ts +++ b/apps/web/src/app/admin-console/organizations/guards/is-paid-org.guard.spec.ts @@ -20,16 +20,19 @@ import { isPaidOrgGuard } from "./is-paid-org.guard"; @Component({ template: "

This is the home screen!

", + standalone: false, }) export class HomescreenComponent {} @Component({ template: "

This component can only be accessed by a paid organization!

", + standalone: false, }) export class PaidOrganizationOnlyComponent {} @Component({ template: "

This is the organization upgrade screen!

", + standalone: false, }) export class OrganizationUpgradeScreenComponent {} diff --git a/apps/web/src/app/admin-console/organizations/guards/org-permissions.guard.spec.ts b/apps/web/src/app/admin-console/organizations/guards/org-permissions.guard.spec.ts index 9afd34ca149..d628e23063f 100644 --- a/apps/web/src/app/admin-console/organizations/guards/org-permissions.guard.spec.ts +++ b/apps/web/src/app/admin-console/organizations/guards/org-permissions.guard.spec.ts @@ -100,20 +100,44 @@ describe("Organization Permissions Guard", () => { it("permits navigation if the user has permissions", async () => { const permissionsCallback = jest.fn(); - permissionsCallback.mockImplementation((_org) => true); + permissionsCallback.mockReturnValue(true); const actual = await TestBed.runInInjectionContext( async () => await organizationPermissionsGuard(permissionsCallback)(route, state), ); - expect(permissionsCallback).toHaveBeenCalledWith(orgFactory({ id: targetOrgId })); + expect(permissionsCallback).toHaveBeenCalledTimes(1); + expect(actual).toBe(true); + }); + + it("handles a Promise returned from the callback", async () => { + const permissionsCallback = jest.fn(); + permissionsCallback.mockReturnValue(Promise.resolve(true)); + + const actual = await TestBed.runInInjectionContext(() => + organizationPermissionsGuard(permissionsCallback)(route, state), + ); + + expect(permissionsCallback).toHaveBeenCalledTimes(1); + expect(actual).toBe(true); + }); + + it("handles an Observable returned from the callback", async () => { + const permissionsCallback = jest.fn(); + permissionsCallback.mockReturnValue(of(true)); + + const actual = await TestBed.runInInjectionContext(() => + organizationPermissionsGuard(permissionsCallback)(route, state), + ); + + expect(permissionsCallback).toHaveBeenCalledTimes(1); expect(actual).toBe(true); }); describe("if the user does not have permissions", () => { it("and there is no Item ID, block navigation", async () => { const permissionsCallback = jest.fn(); - permissionsCallback.mockImplementation((_org) => false); + permissionsCallback.mockReturnValue(false); state = mock({ root: mock({ diff --git a/apps/web/src/app/admin-console/organizations/guards/org-permissions.guard.ts b/apps/web/src/app/admin-console/organizations/guards/org-permissions.guard.ts index d399f9c9c05..6c9090a27b4 100644 --- a/apps/web/src/app/admin-console/organizations/guards/org-permissions.guard.ts +++ b/apps/web/src/app/admin-console/organizations/guards/org-permissions.guard.ts @@ -1,13 +1,13 @@ // FIXME: Update this file to be type safe and remove this and next line // @ts-strict-ignore -import { inject } from "@angular/core"; +import { EnvironmentInjector, inject, runInInjectionContext } from "@angular/core"; import { ActivatedRouteSnapshot, CanActivateFn, Router, RouterStateSnapshot, } from "@angular/router"; -import { firstValueFrom, switchMap } from "rxjs"; +import { firstValueFrom, isObservable, Observable, switchMap } from "rxjs"; import { canAccessOrgAdmin, @@ -42,7 +42,9 @@ import { ToastService } from "@bitwarden/components"; * proceeds as expected. */ export function organizationPermissionsGuard( - permissionsCallback?: (organization: Organization) => boolean, + permissionsCallback?: ( + organization: Organization, + ) => boolean | Promise | Observable, ): CanActivateFn { return async (route: ActivatedRouteSnapshot, state: RouterStateSnapshot) => { const router = inject(Router); @@ -51,6 +53,7 @@ export function organizationPermissionsGuard( const i18nService = inject(I18nService); const syncService = inject(SyncService); const accountService = inject(AccountService); + const environmentInjector = inject(EnvironmentInjector); // TODO: We need to fix issue once and for all. if ((await syncService.getLastSync()) == null) { @@ -78,7 +81,22 @@ export function organizationPermissionsGuard( return router.createUrlTree(["/"]); } - const hasPermissions = permissionsCallback == null || permissionsCallback(org); + if (permissionsCallback == null) { + // No additional permission checks required, allow navigation + return true; + } + + const callbackResult = runInInjectionContext(environmentInjector, () => + permissionsCallback(org), + ); + + const hasPermissions = isObservable(callbackResult) + ? await firstValueFrom(callbackResult) // handles observables + : await Promise.resolve(callbackResult); // handles promises and boolean values + + if (hasPermissions !== true && hasPermissions !== false) { + throw new Error("Permission callback did not resolve to a boolean."); + } if (!hasPermissions) { // Handle linkable ciphers for organizations the user only has view access to diff --git a/apps/web/src/app/admin-console/organizations/guards/org-redirect.guard.spec.ts b/apps/web/src/app/admin-console/organizations/guards/org-redirect.guard.spec.ts index fa348867a86..9dc084484f3 100644 --- a/apps/web/src/app/admin-console/organizations/guards/org-redirect.guard.spec.ts +++ b/apps/web/src/app/admin-console/organizations/guards/org-redirect.guard.spec.ts @@ -19,16 +19,19 @@ import { organizationRedirectGuard } from "./org-redirect.guard"; @Component({ template: "

This is the home screen!

", + standalone: false, }) export class HomescreenComponent {} @Component({ template: "

This is the admin console!

", + standalone: false, }) export class AdminConsoleComponent {} @Component({ template: "

This is a subroute of the admin console!

", + standalone: false, }) export class AdminConsoleSubrouteComponent {} diff --git a/apps/web/src/app/admin-console/organizations/integrations/integrations.component.ts b/apps/web/src/app/admin-console/organizations/integrations/integrations.component.ts index 80c12af8522..e6a62b1db73 100644 --- a/apps/web/src/app/admin-console/organizations/integrations/integrations.component.ts +++ b/apps/web/src/app/admin-console/organizations/integrations/integrations.component.ts @@ -22,7 +22,6 @@ import { Integration } from "../shared/components/integrations/models"; @Component({ selector: "ac-integrations", templateUrl: "./integrations.component.html", - standalone: true, imports: [ SharedModule, SharedOrganizationModule, diff --git a/apps/web/src/app/admin-console/organizations/layouts/organization-layout.component.html b/apps/web/src/app/admin-console/organizations/layouts/organization-layout.component.html index e50c55e83d2..f991678e834 100644 --- a/apps/web/src/app/admin-console/organizations/layouts/organization-layout.component.html +++ b/apps/web/src/app/admin-console/organizations/layouts/organization-layout.component.html @@ -6,6 +6,7 @@ icon="bwi-filter" *ngIf="organization.useRiskInsights" [text]="'accessIntelligence' | i18n" + route="access-intelligence" > @@ -138,22 +139,6 @@ - - {{ "accountDeprovisioningNotification" | i18n }} -
- {{ "learnMore" | i18n }} - - canAccessOrgAdmin(org); - protected domainVerificationNavigationTextKey: string; protected integrationPageEnabled$: Observable; @@ -65,9 +61,9 @@ export class OrganizationLayoutComponent implements OnInit { organizationIsUnmanaged$: Observable; enterpriseOrganization$: Observable; - showAccountDeprovisioningBanner$: Observable; protected isBreadcrumbEventLogsEnabled$: Observable; protected showSponsoredFamiliesDropdown$: Observable; + protected canShowPoliciesTab$: Observable; constructor( private route: ActivatedRoute, @@ -76,9 +72,9 @@ export class OrganizationLayoutComponent implements OnInit { private configService: ConfigService, private policyService: PolicyService, private providerService: ProviderService, - protected bannerService: AccountDeprovisioningBannerService, private accountService: AccountService, private freeFamiliesPolicyService: FreeFamiliesPolicyService, + private organizationBillingService: OrganizationBillingServiceAbstraction, ) {} async ngOnInit() { @@ -98,20 +94,6 @@ export class OrganizationLayoutComponent implements OnInit { this.showSponsoredFamiliesDropdown$ = this.freeFamiliesPolicyService.showSponsoredFamiliesDropdown$(this.organization$); - this.showAccountDeprovisioningBanner$ = combineLatest([ - this.bannerService.showBanner$, - this.configService.getFeatureFlag$(FeatureFlag.AccountDeprovisioningBanner), - this.organization$, - ]).pipe( - map( - ([dismissedOrgs, featureFlagEnabled, organization]) => - organization.productTierType === ProductTierType.Enterprise && - organization.isAdmin && - !dismissedOrgs?.includes(organization.id) && - featureFlagEnabled, - ), - ); - this.canAccessExport$ = this.organization$.pipe(map((org) => org.canAccessExport)); this.showPaymentAndHistory$ = this.organization$.pipe( @@ -143,11 +125,17 @@ export class OrganizationLayoutComponent implements OnInit { this.integrationPageEnabled$ = this.organization$.pipe(map((org) => org.canAccessIntegrations)); - this.domainVerificationNavigationTextKey = (await this.configService.getFeatureFlag( - FeatureFlag.AccountDeprovisioning, - )) - ? "claimedDomains" - : "domainVerification"; + this.canShowPoliciesTab$ = this.organization$.pipe( + switchMap((organization) => + this.organizationBillingService + .isBreadcrumbingPoliciesEnabled$(organization) + .pipe( + map( + (isBreadcrumbingEnabled) => isBreadcrumbingEnabled || organization.canManagePolicies, + ), + ), + ), + ); } canShowVaultTab(organization: Organization): boolean { diff --git a/apps/web/src/app/admin-console/organizations/layouts/services/account-deprovisioning-banner.service.spec.ts b/apps/web/src/app/admin-console/organizations/layouts/services/account-deprovisioning-banner.service.spec.ts deleted file mode 100644 index 414828969df..00000000000 --- a/apps/web/src/app/admin-console/organizations/layouts/services/account-deprovisioning-banner.service.spec.ts +++ /dev/null @@ -1,75 +0,0 @@ -import { firstValueFrom } from "rxjs"; - -import { Organization } from "@bitwarden/common/admin-console/models/domain/organization"; -import { Utils } from "@bitwarden/common/platform/misc/utils"; -import { - FakeAccountService, - FakeStateProvider, - mockAccountServiceWith, -} from "@bitwarden/common/spec"; -import { UserId } from "@bitwarden/common/types/guid"; - -import { AccountDeprovisioningBannerService } from "./account-deprovisioning-banner.service"; - -describe("Account Deprovisioning Banner Service", () => { - const userId = Utils.newGuid() as UserId; - let accountService: FakeAccountService; - let stateProvider: FakeStateProvider; - let bannerService: AccountDeprovisioningBannerService; - - beforeEach(async () => { - accountService = mockAccountServiceWith(userId); - stateProvider = new FakeStateProvider(accountService); - bannerService = new AccountDeprovisioningBannerService(stateProvider); - }); - - it("updates state with single org", async () => { - const fakeOrg = new Organization(); - fakeOrg.id = "123"; - - await bannerService.hideBanner(fakeOrg); - const state = await firstValueFrom(bannerService.showBanner$); - - expect(state).toEqual([fakeOrg.id]); - }); - - it("updates state with multiple orgs", async () => { - const fakeOrg1 = new Organization(); - fakeOrg1.id = "123"; - const fakeOrg2 = new Organization(); - fakeOrg2.id = "234"; - const fakeOrg3 = new Organization(); - fakeOrg3.id = "987"; - - await bannerService.hideBanner(fakeOrg1); - await bannerService.hideBanner(fakeOrg2); - await bannerService.hideBanner(fakeOrg3); - - const state = await firstValueFrom(bannerService.showBanner$); - - expect(state).toContain(fakeOrg1.id); - expect(state).toContain(fakeOrg2.id); - expect(state).toContain(fakeOrg3.id); - }); - - it("does not add the same org id multiple times", async () => { - const fakeOrg = new Organization(); - fakeOrg.id = "123"; - - await bannerService.hideBanner(fakeOrg); - await bannerService.hideBanner(fakeOrg); - - const state = await firstValueFrom(bannerService.showBanner$); - - expect(state).toEqual([fakeOrg.id]); - }); - - it("does not add null to the state", async () => { - await bannerService.hideBanner(null as unknown as Organization); - await bannerService.hideBanner(undefined as unknown as Organization); - - const state = await firstValueFrom(bannerService.showBanner$); - - expect(state).toBeNull(); - }); -}); diff --git a/apps/web/src/app/admin-console/organizations/layouts/services/account-deprovisioning-banner.service.ts b/apps/web/src/app/admin-console/organizations/layouts/services/account-deprovisioning-banner.service.ts deleted file mode 100644 index 86a6b7df3e2..00000000000 --- a/apps/web/src/app/admin-console/organizations/layouts/services/account-deprovisioning-banner.service.ts +++ /dev/null @@ -1,40 +0,0 @@ -import { Injectable } from "@angular/core"; - -import { Organization } from "@bitwarden/common/admin-console/models/domain/organization"; -import { - ACCOUNT_DEPROVISIONING_BANNER_DISK, - StateProvider, - UserKeyDefinition, -} from "@bitwarden/common/platform/state"; - -export const SHOW_BANNER_KEY = new UserKeyDefinition( - ACCOUNT_DEPROVISIONING_BANNER_DISK, - "accountDeprovisioningBanner", - { - deserializer: (b) => b, - clearOn: [], - }, -); - -@Injectable({ providedIn: "root" }) -export class AccountDeprovisioningBannerService { - private _showBanner = this.stateProvider.getActive(SHOW_BANNER_KEY); - - showBanner$ = this._showBanner.state$; - - constructor(private stateProvider: StateProvider) {} - - async hideBanner(organization: Organization) { - await this._showBanner.update((state) => { - if (!organization) { - return state; - } - if (!state) { - return [organization.id]; - } else if (!state.includes(organization.id)) { - return [...state, organization.id]; - } - return state; - }); - } -} diff --git a/apps/web/src/app/admin-console/organizations/manage/entity-events.component.ts b/apps/web/src/app/admin-console/organizations/manage/entity-events.component.ts index 4eab2969fff..10f68695e88 100644 --- a/apps/web/src/app/admin-console/organizations/manage/entity-events.component.ts +++ b/apps/web/src/app/admin-console/organizations/manage/entity-events.component.ts @@ -38,7 +38,6 @@ export interface EntityEventsDialogParams { @Component({ imports: [SharedModule], templateUrl: "entity-events.component.html", - standalone: true, }) export class EntityEventsComponent implements OnInit, OnDestroy { loading = true; 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/admin-console/organizations/manage/organization-trust.component.ts b/apps/web/src/app/admin-console/organizations/manage/organization-trust.component.ts deleted file mode 100644 index 3f013c9fc74..00000000000 --- a/apps/web/src/app/admin-console/organizations/manage/organization-trust.component.ts +++ /dev/null @@ -1,69 +0,0 @@ -import { DialogRef, DIALOG_DATA } from "@angular/cdk/dialog"; -import { Component, OnInit, Inject } from "@angular/core"; -import { FormBuilder } from "@angular/forms"; - -import { OrganizationManagementPreferencesService } from "@bitwarden/common/admin-console/abstractions/organization-management-preferences/organization-management-preferences.service"; -import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; -import { DialogService } from "@bitwarden/components"; -import { KeyService } from "@bitwarden/key-management"; - -type OrganizationTrustDialogData = { - /** display name of the organization */ - name: string; - /** identifies the organization */ - orgId: string; - /** org public key */ - publicKey: Uint8Array; -}; -@Component({ - selector: "organization-trust", - templateUrl: "organization-trust.component.html", -}) -export class OrganizationTrustComponent implements OnInit { - loading = true; - fingerprint: string = ""; - confirmForm = this.formBuilder.group({}); - - constructor( - @Inject(DIALOG_DATA) protected params: OrganizationTrustDialogData, - private formBuilder: FormBuilder, - private keyService: KeyService, - protected organizationManagementPreferencesService: OrganizationManagementPreferencesService, - private logService: LogService, - private dialogRef: DialogRef, - ) {} - - async ngOnInit() { - try { - const fingerprint = await this.keyService.getFingerprint( - this.params.orgId, - this.params.publicKey, - ); - if (fingerprint != null) { - this.fingerprint = fingerprint.join("-"); - } - } catch (e) { - this.logService.error(e); - } - this.loading = false; - } - - submit = async () => { - if (this.loading) { - return; - } - - this.dialogRef.close(true); - }; - - /** - * Strongly typed helper to open a OrganizationTrustComponent - * @param dialogService Instance of the dialog service that will be used to open the dialog - * @param data The data to pass to the dialog - */ - static open(dialogService: DialogService, data: OrganizationTrustDialogData) { - return dialogService.open(OrganizationTrustComponent, { - data, - }); - } -} diff --git a/apps/web/src/app/admin-console/organizations/manage/user-confirm.component.ts b/apps/web/src/app/admin-console/organizations/manage/user-confirm.component.ts index b5068ba55a6..03b77cfaa71 100644 --- a/apps/web/src/app/admin-console/organizations/manage/user-confirm.component.ts +++ b/apps/web/src/app/admin-console/organizations/manage/user-confirm.component.ts @@ -18,6 +18,7 @@ export type UserConfirmDialogData = { @Component({ selector: "app-user-confirm", templateUrl: "user-confirm.component.html", + standalone: false, }) export class UserConfirmComponent implements OnInit { name: string; diff --git a/apps/web/src/app/admin-console/organizations/manage/verify-recover-delete-org.component.ts b/apps/web/src/app/admin-console/organizations/manage/verify-recover-delete-org.component.ts index 6dcdff00160..f88eb82e529 100644 --- a/apps/web/src/app/admin-console/organizations/manage/verify-recover-delete-org.component.ts +++ b/apps/web/src/app/admin-console/organizations/manage/verify-recover-delete-org.component.ts @@ -14,7 +14,6 @@ import { SharedModule } from "../../../shared/shared.module"; @Component({ templateUrl: "verify-recover-delete-org.component.html", - standalone: true, imports: [SharedModule], }) export class VerifyRecoverDeleteOrgComponent implements OnInit { diff --git a/apps/web/src/app/admin-console/organizations/members/components/bulk/bulk-confirm-dialog.component.ts b/apps/web/src/app/admin-console/organizations/members/components/bulk/bulk-confirm-dialog.component.ts index c19984f980d..4ec50799ae0 100644 --- a/apps/web/src/app/admin-console/organizations/members/components/bulk/bulk-confirm-dialog.component.ts +++ b/apps/web/src/app/admin-console/organizations/members/components/bulk/bulk-confirm-dialog.component.ts @@ -33,6 +33,7 @@ type BulkConfirmDialogParams = { @Component({ templateUrl: "bulk-confirm-dialog.component.html", + standalone: false, }) export class BulkConfirmDialogComponent extends BaseBulkConfirmComponent { organizationId: string; diff --git a/apps/web/src/app/admin-console/organizations/members/components/bulk/bulk-delete-dialog.component.ts b/apps/web/src/app/admin-console/organizations/members/components/bulk/bulk-delete-dialog.component.ts index 27caea3ebd3..8fb60e85b08 100644 --- a/apps/web/src/app/admin-console/organizations/members/components/bulk/bulk-delete-dialog.component.ts +++ b/apps/web/src/app/admin-console/organizations/members/components/bulk/bulk-delete-dialog.component.ts @@ -1,12 +1,9 @@ // FIXME: Update this file to be type safe and remove this and next line // @ts-strict-ignore import { Component, Inject } from "@angular/core"; -import { firstValueFrom } from "rxjs"; import { OrganizationUserApiService } from "@bitwarden/admin-console/common"; import { OrganizationUserStatusType } from "@bitwarden/common/admin-console/enums"; -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 { DIALOG_DATA, DialogConfig, DialogService } from "@bitwarden/components"; @@ -21,6 +18,7 @@ type BulkDeleteDialogParams = { @Component({ templateUrl: "bulk-delete-dialog.component.html", + standalone: false, }) export class BulkDeleteDialogComponent { organizationId: string; @@ -35,7 +33,6 @@ export class BulkDeleteDialogComponent { @Inject(DIALOG_DATA) protected dialogParams: BulkDeleteDialogParams, protected i18nService: I18nService, private organizationUserApiService: OrganizationUserApiService, - private configService: ConfigService, private deleteManagedMemberWarningService: DeleteManagedMemberWarningService, ) { this.organizationId = dialogParams.organizationId; @@ -43,11 +40,7 @@ export class BulkDeleteDialogComponent { } async submit() { - if ( - await firstValueFrom(this.configService.getFeatureFlag$(FeatureFlag.AccountDeprovisioning)) - ) { - await this.deleteManagedMemberWarningService.acknowledgeWarning(this.organizationId); - } + await this.deleteManagedMemberWarningService.acknowledgeWarning(this.organizationId); try { this.loading = true; diff --git a/apps/web/src/app/admin-console/organizations/members/components/bulk/bulk-enable-sm-dialog.component.ts b/apps/web/src/app/admin-console/organizations/members/components/bulk/bulk-enable-sm-dialog.component.ts index e01809789f3..9132625c587 100644 --- a/apps/web/src/app/admin-console/organizations/members/components/bulk/bulk-enable-sm-dialog.component.ts +++ b/apps/web/src/app/admin-console/organizations/members/components/bulk/bulk-enable-sm-dialog.component.ts @@ -22,6 +22,7 @@ export type BulkEnableSecretsManagerDialogData = { @Component({ templateUrl: `bulk-enable-sm-dialog.component.html`, + standalone: false, }) export class BulkEnableSecretsManagerDialogComponent implements OnInit { protected dataSource = new TableDataSource(); diff --git a/apps/web/src/app/admin-console/organizations/members/components/bulk/bulk-remove-dialog.component.ts b/apps/web/src/app/admin-console/organizations/members/components/bulk/bulk-remove-dialog.component.ts index 00711e355cb..5bbc6f093f0 100644 --- a/apps/web/src/app/admin-console/organizations/members/components/bulk/bulk-remove-dialog.component.ts +++ b/apps/web/src/app/admin-console/organizations/members/components/bulk/bulk-remove-dialog.component.ts @@ -21,6 +21,7 @@ type BulkRemoveDialogParams = { @Component({ templateUrl: "bulk-remove-dialog.component.html", + standalone: false, }) export class BulkRemoveDialogComponent extends BaseBulkRemoveComponent { organizationId: string; diff --git a/apps/web/src/app/admin-console/organizations/members/components/bulk/bulk-restore-revoke.component.html b/apps/web/src/app/admin-console/organizations/members/components/bulk/bulk-restore-revoke.component.html index 4925c210039..1b711b366d6 100644 --- a/apps/web/src/app/admin-console/organizations/members/components/bulk/bulk-restore-revoke.component.html +++ b/apps/web/src/app/admin-console/organizations/members/components/bulk/bulk-restore-revoke.component.html @@ -1,12 +1,6 @@ - + - {{ bulkMemberTitle }} - - {{ bulkTitle }} - + {{ bulkTitle }}
@@ -20,7 +14,7 @@ {{ "nonCompliantMembersError" | i18n }} @@ -50,7 +44,7 @@ - {{ (accountDeprovisioning.enabled ? "member" : "user") | i18n }} + {{ "member" | i18n }} {{ "details" | i18n }} @@ -82,7 +76,7 @@ - {{ (accountDeprovisioning.enabled ? "member" : "user") | i18n }} + {{ "member" | i18n }} {{ "status" | i18n }} @@ -113,7 +107,7 @@ [bitAction]="submit" buttonType="primary" > - {{ accountDeprovisioning.enabled ? bulkMemberTitle : bulkTitle }} + {{ bulkTitle }} @@ -16,6 +23,7 @@ [selected]="status" (selectedChange)="statusToggle.next($event)" [attr.aria-label]="'memberStatusFilter' | i18n" + *ngIf="showUserManagementControls$ | async" > {{ "all" | i18n }} @@ -71,7 +79,7 @@ - + @@ -174,74 +183,143 @@ alignContent="middle" [ngClass]="rowHeightClass" > - + - -
- -
-
- - - {{ "invited" | i18n }} - - - {{ "needsConfirmation" | i18n }} - - - {{ "revoked" | i18n }} - -
-
- {{ u.email }} + + +
+ +
+
+ + + {{ "invited" | i18n }} + + + {{ "needsConfirmation" | i18n }} + + + {{ "revoked" | i18n }} + +
+
+ {{ u.email }} +
-
- + + + + +
+ +
+
+ {{ u.name ?? u.email }} + + {{ "invited" | i18n }} + + + {{ "needsConfirmation" | i18n }} + + + {{ "revoked" | i18n }} + +
+
+ {{ u.email }} +
+
+
+ +
- - - + + + + + + + + + + - - {{ u.type | userType }} - + + + {{ u.type | userType }} + + + + + {{ u.type | userType }} + + @@ -271,53 +349,58 @@ > - - - - - - - - + + + + + + + + + + + + - - - - + + + + + + + @@ -373,4 +459,3 @@ - diff --git a/apps/web/src/app/admin-console/organizations/members/members.component.ts b/apps/web/src/app/admin-console/organizations/members/members.component.ts index 3ce78354046..4f453762b5d 100644 --- a/apps/web/src/app/admin-console/organizations/members/members.component.ts +++ b/apps/web/src/app/admin-console/organizations/members/members.component.ts @@ -1,6 +1,6 @@ // FIXME: Update this file to be type safe and remove this and next line // @ts-strict-ignore -import { Component, OnInit, ViewChild, ViewContainerRef } from "@angular/core"; +import { Component } from "@angular/core"; import { takeUntilDestroyed } from "@angular/core/rxjs-interop"; import { ActivatedRoute, Router } from "@angular/router"; import { @@ -90,11 +90,9 @@ class MembersTableDataSource extends PeopleTableDataSource @Component({ templateUrl: "members.component.html", + standalone: false, }) -export class MembersComponent extends BaseMembersComponent implements OnInit { - @ViewChild("resetPasswordTemplate", { read: ViewContainerRef, static: true }) - resetPasswordModalRef: ViewContainerRef; - +export class MembersComponent extends BaseMembersComponent { userType = OrganizationUserType; userStatusType = OrganizationUserStatusType; memberTab = MemberDialogTab; @@ -104,16 +102,18 @@ export class MembersComponent extends BaseMembersComponent status: OrganizationUserStatusType = null; orgResetPasswordPolicyEnabled = false; orgIsOnSecretsManagerStandalone = false; - accountDeprovisioningEnabled = false; protected canUseSecretsManager$: Observable; + protected showUserManagementControls$: Observable; // Fixed sizes used for cdkVirtualScroll protected rowHeight = 69; protected rowHeightClass = `tw-h-[69px]`; + private organizationUsersCount = 0; + get occupiedSeatCount(): number { - return this.dataSource.activeUserCount; + return this.organizationUsersCount; } constructor( @@ -139,8 +139,8 @@ export class MembersComponent extends BaseMembersComponent private groupService: GroupApiService, private collectionService: CollectionService, private billingApiService: BillingApiServiceAbstraction, - private configService: ConfigService, protected deleteManagedMemberWarningService: DeleteManagedMemberWarningService, + private configService: ConfigService, ) { super( apiService, @@ -220,6 +220,7 @@ export class MembersComponent extends BaseMembersComponent ); this.orgIsOnSecretsManagerStandalone = billingMetadata.isOnSecretsManagerStandalone; + this.organizationUsersCount = billingMetadata.organizationOccupiedSeats; await this.load(); @@ -235,11 +236,16 @@ export class MembersComponent extends BaseMembersComponent takeUntilDestroyed(), ) .subscribe(); - } - async ngOnInit() { - this.accountDeprovisioningEnabled = await this.configService.getFeatureFlag( - FeatureFlag.AccountDeprovisioning, + // Setup feature flag-dependent observables + const separateCustomRolePermissionsEnabled$ = this.configService.getFeatureFlag$( + FeatureFlag.SeparateCustomRolePermissions, + ); + this.showUserManagementControls$ = separateCustomRolePermissionsEnabled$.pipe( + map( + (separateCustomRolePermissionsEnabled) => + !separateCustomRolePermissionsEnabled || this.organization.canManageUsers, + ), ); } @@ -591,20 +597,18 @@ export class MembersComponent extends BaseMembersComponent } async bulkDelete() { - if (this.accountDeprovisioningEnabled) { - const warningAcknowledged = await firstValueFrom( - this.deleteManagedMemberWarningService.warningAcknowledged(this.organization.id), - ); + const warningAcknowledged = await firstValueFrom( + this.deleteManagedMemberWarningService.warningAcknowledged(this.organization.id), + ); - if ( - !warningAcknowledged && - this.organization.canManageUsers && - this.organization.productTierType === ProductTierType.Enterprise - ) { - const acknowledged = await this.deleteManagedMemberWarningService.showWarning(); - if (!acknowledged) { - return; - } + if ( + !warningAcknowledged && + this.organization.canManageUsers && + this.organization.productTierType === ProductTierType.Enterprise + ) { + const acknowledged = await this.deleteManagedMemberWarningService.showWarning(); + if (!acknowledged) { + return; } } @@ -794,20 +798,18 @@ export class MembersComponent extends BaseMembersComponent } async deleteUser(user: OrganizationUserView) { - if (this.accountDeprovisioningEnabled) { - const warningAcknowledged = await firstValueFrom( - this.deleteManagedMemberWarningService.warningAcknowledged(this.organization.id), - ); + const warningAcknowledged = await firstValueFrom( + this.deleteManagedMemberWarningService.warningAcknowledged(this.organization.id), + ); - if ( - !warningAcknowledged && - this.organization.canManageUsers && - this.organization.productTierType === ProductTierType.Enterprise - ) { - const acknowledged = await this.deleteManagedMemberWarningService.showWarning(); - if (!acknowledged) { - return false; - } + if ( + !warningAcknowledged && + this.organization.canManageUsers && + this.organization.productTierType === ProductTierType.Enterprise + ) { + const acknowledged = await this.deleteManagedMemberWarningService.showWarning(); + if (!acknowledged) { + return false; } } @@ -829,9 +831,7 @@ export class MembersComponent extends BaseMembersComponent return false; } - if (this.accountDeprovisioningEnabled) { - await this.deleteManagedMemberWarningService.acknowledgeWarning(this.organization.id); - } + await this.deleteManagedMemberWarningService.acknowledgeWarning(this.organization.id); this.actionPromise = this.organizationUserApiService.deleteOrganizationUser( this.organization.id, @@ -864,56 +864,23 @@ export class MembersComponent extends BaseMembersComponent }); } - get showBulkConfirmUsers(): boolean { - if (!this.accountDeprovisioningEnabled) { - return super.showBulkConfirmUsers; - } - - return this.dataSource - .getCheckedUsers() - .every((member) => member.status == this.userStatusType.Accepted); - } - - get showBulkReinviteUsers(): boolean { - if (!this.accountDeprovisioningEnabled) { - return super.showBulkReinviteUsers; - } - - return this.dataSource - .getCheckedUsers() - .every((member) => member.status == this.userStatusType.Invited); - } - get showBulkRestoreUsers(): boolean { - return ( - !this.accountDeprovisioningEnabled || - this.dataSource - .getCheckedUsers() - .every((member) => member.status == this.userStatusType.Revoked) - ); + return this.dataSource + .getCheckedUsers() + .every((member) => member.status == this.userStatusType.Revoked); } get showBulkRevokeUsers(): boolean { - return ( - !this.accountDeprovisioningEnabled || - this.dataSource - .getCheckedUsers() - .every((member) => member.status != this.userStatusType.Revoked) - ); + return this.dataSource + .getCheckedUsers() + .every((member) => member.status != this.userStatusType.Revoked); } get showBulkRemoveUsers(): boolean { - return ( - !this.accountDeprovisioningEnabled || - this.dataSource.getCheckedUsers().every((member) => !member.managedByOrganization) - ); + return this.dataSource.getCheckedUsers().every((member) => !member.managedByOrganization); } get showBulkDeleteUsers(): boolean { - if (!this.accountDeprovisioningEnabled) { - return false; - } - const validStatuses = [ this.userStatusType.Accepted, this.userStatusType.Confirmed, diff --git a/apps/web/src/app/admin-console/organizations/members/services/organization-user-reset-password/organization-user-reset-password.service.ts b/apps/web/src/app/admin-console/organizations/members/services/organization-user-reset-password/organization-user-reset-password.service.ts index 78d2d8fd165..ecf4d26eb52 100644 --- a/apps/web/src/app/admin-console/organizations/members/services/organization-user-reset-password/organization-user-reset-password.service.ts +++ b/apps/web/src/app/admin-console/organizations/members/services/organization-user-reset-password/organization-user-reset-password.service.ts @@ -112,7 +112,7 @@ export class OrganizationUserResetPasswordService if (orgSymKey == null) { throw new Error("No org key found"); } - const decPrivateKey = await this.encryptService.decryptToBytes( + const decPrivateKey = await this.encryptService.unwrapDecapsulationKey( new EncString(response.encryptedPrivateKey), orgSymKey, ); diff --git a/apps/web/src/app/admin-console/organizations/organization-routing.module.ts b/apps/web/src/app/admin-console/organizations/organization-routing.module.ts index e5c68b73546..4d8971f74fd 100644 --- a/apps/web/src/app/admin-console/organizations/organization-routing.module.ts +++ b/apps/web/src/app/admin-console/organizations/organization-routing.module.ts @@ -14,7 +14,7 @@ import { } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction"; import { Organization } from "@bitwarden/common/admin-console/models/domain/organization"; -import { deepLinkGuard } from "../../auth/guards/deep-link.guard"; +import { deepLinkGuard } from "../../auth/guards/deep-link/deep-link.guard"; import { VaultModule } from "./collections/vault.module"; import { isEnterpriseOrgGuard } from "./guards/is-enterprise-org.guard"; diff --git a/apps/web/src/app/admin-console/organizations/policies/disable-send.component.ts b/apps/web/src/app/admin-console/organizations/policies/disable-send.component.ts index 2acf175e3da..b323ac00d34 100644 --- a/apps/web/src/app/admin-console/organizations/policies/disable-send.component.ts +++ b/apps/web/src/app/admin-console/organizations/policies/disable-send.component.ts @@ -14,5 +14,6 @@ export class DisableSendPolicy extends BasePolicy { @Component({ selector: "policy-disable-send", templateUrl: "disable-send.component.html", + standalone: false, }) export class DisableSendPolicyComponent extends BasePolicyComponent {} diff --git a/apps/web/src/app/admin-console/organizations/policies/index.ts b/apps/web/src/app/admin-console/organizations/policies/index.ts index 20137105993..4f4b85fc6c2 100644 --- a/apps/web/src/app/admin-console/organizations/policies/index.ts +++ b/apps/web/src/app/admin-console/organizations/policies/index.ts @@ -11,3 +11,4 @@ export { SingleOrgPolicy } from "./single-org.component"; export { TwoFactorAuthenticationPolicy } from "./two-factor-authentication.component"; export { PoliciesComponent } from "./policies.component"; export { RemoveUnlockWithPinPolicy } from "./remove-unlock-with-pin.component"; +export { RestrictedItemTypesPolicy } from "./restricted-item-types.component"; diff --git a/apps/web/src/app/admin-console/organizations/policies/master-password.component.ts b/apps/web/src/app/admin-console/organizations/policies/master-password.component.ts index 328989df66b..54cf1be88fc 100644 --- a/apps/web/src/app/admin-console/organizations/policies/master-password.component.ts +++ b/apps/web/src/app/admin-console/organizations/policies/master-password.component.ts @@ -28,6 +28,7 @@ export class MasterPasswordPolicy extends BasePolicy { @Component({ selector: "policy-master-password", templateUrl: "master-password.component.html", + standalone: false, }) export class MasterPasswordPolicyComponent extends BasePolicyComponent implements OnInit { MinPasswordLength = Utils.minimumPasswordLength; diff --git a/apps/web/src/app/admin-console/organizations/policies/password-generator.component.ts b/apps/web/src/app/admin-console/organizations/policies/password-generator.component.ts index 4439f974e55..26f87f333eb 100644 --- a/apps/web/src/app/admin-console/organizations/policies/password-generator.component.ts +++ b/apps/web/src/app/admin-console/organizations/policies/password-generator.component.ts @@ -7,7 +7,7 @@ import { BehaviorSubject, map } from "rxjs"; import { PolicyType } from "@bitwarden/common/admin-console/enums"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; -import { Generators } from "@bitwarden/generator-core"; +import { BuiltIn, Profile } from "@bitwarden/generator-core"; import { BasePolicy, BasePolicyComponent } from "./base-policy.component"; @@ -21,18 +21,27 @@ export class PasswordGeneratorPolicy extends BasePolicy { @Component({ selector: "policy-password-generator", templateUrl: "password-generator.component.html", + standalone: false, }) export class PasswordGeneratorPolicyComponent extends BasePolicyComponent { // these properties forward the application default settings to the UI // for HTML attribute bindings - protected readonly minLengthMin = Generators.password.settings.constraints.length.min; - protected readonly minLengthMax = Generators.password.settings.constraints.length.max; - protected readonly minNumbersMin = Generators.password.settings.constraints.minNumber.min; - protected readonly minNumbersMax = Generators.password.settings.constraints.minNumber.max; - protected readonly minSpecialMin = Generators.password.settings.constraints.minSpecial.min; - protected readonly minSpecialMax = Generators.password.settings.constraints.minSpecial.max; - protected readonly minNumberWordsMin = Generators.passphrase.settings.constraints.numWords.min; - protected readonly minNumberWordsMax = Generators.passphrase.settings.constraints.numWords.max; + protected readonly minLengthMin = + BuiltIn.password.profiles[Profile.account].constraints.default.length.min; + protected readonly minLengthMax = + BuiltIn.password.profiles[Profile.account].constraints.default.length.max; + protected readonly minNumbersMin = + BuiltIn.password.profiles[Profile.account].constraints.default.minNumber.min; + protected readonly minNumbersMax = + BuiltIn.password.profiles[Profile.account].constraints.default.minNumber.max; + protected readonly minSpecialMin = + BuiltIn.password.profiles[Profile.account].constraints.default.minSpecial.min; + protected readonly minSpecialMax = + BuiltIn.password.profiles[Profile.account].constraints.default.minSpecial.max; + protected readonly minNumberWordsMin = + BuiltIn.passphrase.profiles[Profile.account].constraints.default.numWords.min; + protected readonly minNumberWordsMax = + BuiltIn.passphrase.profiles[Profile.account].constraints.default.numWords.max; data = this.formBuilder.group({ overridePasswordType: [null], diff --git a/apps/web/src/app/admin-console/organizations/policies/personal-ownership.component.ts b/apps/web/src/app/admin-console/organizations/policies/personal-ownership.component.ts index 36c79a61fe4..ef92ee90581 100644 --- a/apps/web/src/app/admin-console/organizations/policies/personal-ownership.component.ts +++ b/apps/web/src/app/admin-console/organizations/policies/personal-ownership.component.ts @@ -14,5 +14,6 @@ export class PersonalOwnershipPolicy extends BasePolicy { @Component({ selector: "policy-personal-ownership", templateUrl: "personal-ownership.component.html", + standalone: false, }) export class PersonalOwnershipPolicyComponent extends BasePolicyComponent {} diff --git a/apps/web/src/app/admin-console/organizations/policies/policies.component.html b/apps/web/src/app/admin-console/organizations/policies/policies.component.html index 24021bb765f..016d631019e 100644 --- a/apps/web/src/app/admin-console/organizations/policies/policies.component.html +++ b/apps/web/src/app/admin-console/organizations/policies/policies.component.html @@ -1,4 +1,17 @@ - + + @let organization = organization$ | async; + + @@ -22,5 +35,4 @@ - diff --git a/apps/web/src/app/admin-console/organizations/policies/policies.component.ts b/apps/web/src/app/admin-console/organizations/policies/policies.component.ts index 52cb4da107a..8b6894871bd 100644 --- a/apps/web/src/app/admin-console/organizations/policies/policies.component.ts +++ b/apps/web/src/app/admin-console/organizations/policies/policies.component.ts @@ -1,9 +1,9 @@ // FIXME: Update this file to be type safe and remove this and next line // @ts-strict-ignore -import { Component, OnInit, ViewChild, ViewContainerRef } from "@angular/core"; +import { Component, OnInit } from "@angular/core"; import { ActivatedRoute } from "@angular/router"; -import { firstValueFrom, lastValueFrom } from "rxjs"; -import { first, map } from "rxjs/operators"; +import { firstValueFrom, lastValueFrom, map, Observable, switchMap } from "rxjs"; +import { first } from "rxjs/operators"; import { getOrganizationById, @@ -14,36 +14,46 @@ import { PolicyType } from "@bitwarden/common/admin-console/enums"; import { Organization } from "@bitwarden/common/admin-console/models/domain/organization"; import { PolicyResponse } from "@bitwarden/common/admin-console/models/response/policy.response"; import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; +import { OrganizationBillingServiceAbstraction } from "@bitwarden/common/billing/abstractions"; +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 { + ChangePlanDialogResultType, + openChangePlanDialog, +} from "@bitwarden/web-vault/app/billing/organizations/change-plan-dialog.component"; +import { All } from "@bitwarden/web-vault/app/vault/individual-vault/vault-filter/shared/models/routed-vault-filter.model"; import { PolicyListService } from "../../core/policy-list.service"; -import { BasePolicy } from "../policies"; +import { BasePolicy, RestrictedItemTypesPolicy } from "../policies"; +import { CollectionDialogTabType } from "../shared/components/collection-dialog"; import { PolicyEditComponent, PolicyEditDialogResult } from "./policy-edit.component"; @Component({ selector: "app-org-policies", templateUrl: "policies.component.html", + standalone: false, }) export class PoliciesComponent implements OnInit { - @ViewChild("editTemplate", { read: ViewContainerRef, static: true }) - editModalRef: ViewContainerRef; - loading = true; organizationId: string; policies: BasePolicy[]; - organization: Organization; + protected organization$: Observable; private orgPolicies: PolicyResponse[]; protected policiesEnabledMap: Map = new Map(); + protected isBreadcrumbingEnabled$: Observable; constructor( private route: ActivatedRoute, - private organizationService: OrganizationService, private accountService: AccountService, + private organizationService: OrganizationService, private policyApiService: PolicyApiServiceAbstraction, private policyListService: PolicyListService, + private organizationBillingService: OrganizationBillingServiceAbstraction, private dialogService: DialogService, + private configService: ConfigService, ) {} async ngOnInit() { @@ -53,11 +63,9 @@ export class PoliciesComponent implements OnInit { const userId = await firstValueFrom( this.accountService.activeAccount$.pipe(map((a) => a?.id)), ); - this.organization = await firstValueFrom( - this.organizationService - .organizations$(userId) - .pipe(getOrganizationById(this.organizationId)), - ); + this.organization$ = this.organizationService + .organizations$(userId) + .pipe(getOrganizationById(this.organizationId)); this.policies = this.policyListService.getPolicies(); await this.load(); @@ -86,12 +94,22 @@ export class PoliciesComponent implements OnInit { } async load() { + if ( + (await this.configService.getFeatureFlag(FeatureFlag.RemoveCardItemTypePolicy)) && + this.policyListService.getPolicies().every((p) => !(p instanceof RestrictedItemTypesPolicy)) + ) { + this.policyListService.addPolicies([new RestrictedItemTypesPolicy()]); + } const response = await this.policyApiService.getPolicies(this.organizationId); this.orgPolicies = response.data != null && response.data.length > 0 ? response.data : []; this.orgPolicies.forEach((op) => { this.policiesEnabledMap.set(op.type, op.enabled); }); - + this.isBreadcrumbingEnabled$ = this.organization$.pipe( + switchMap((organization) => + this.organizationBillingService.isBreadcrumbingPoliciesEnabled$(organization), + ), + ); this.loading = false; } @@ -104,8 +122,34 @@ export class PoliciesComponent implements OnInit { }); const result = await lastValueFrom(dialogRef.closed); - if (result === PolicyEditDialogResult.Saved) { - await this.load(); + switch (result) { + case PolicyEditDialogResult.Saved: + await this.load(); + break; + case PolicyEditDialogResult.UpgradePlan: + await this.changePlan(await firstValueFrom(this.organization$)); + break; } } + + protected readonly CollectionDialogTabType = CollectionDialogTabType; + protected readonly All = All; + + protected async changePlan(organization: Organization) { + const reference = openChangePlanDialog(this.dialogService, { + data: { + organizationId: organization.id, + subscription: null, + productTierType: organization.productTierType, + }, + }); + + const result = await lastValueFrom(reference.closed); + + if (result === ChangePlanDialogResultType.Closed) { + return; + } + + await this.load(); + } } diff --git a/apps/web/src/app/admin-console/organizations/policies/policies.module.ts b/apps/web/src/app/admin-console/organizations/policies/policies.module.ts index 1b8ec5089f9..4ecf8d76491 100644 --- a/apps/web/src/app/admin-console/organizations/policies/policies.module.ts +++ b/apps/web/src/app/admin-console/organizations/policies/policies.module.ts @@ -11,6 +11,7 @@ import { PolicyEditComponent } from "./policy-edit.component"; import { RemoveUnlockWithPinPolicyComponent } from "./remove-unlock-with-pin.component"; import { RequireSsoPolicyComponent } from "./require-sso.component"; import { ResetPasswordPolicyComponent } from "./reset-password.component"; +import { RestrictedItemTypesPolicyComponent } from "./restricted-item-types.component"; import { SendOptionsPolicyComponent } from "./send-options.component"; import { SingleOrgPolicyComponent } from "./single-org.component"; import { TwoFactorAuthenticationPolicyComponent } from "./two-factor-authentication.component"; @@ -30,6 +31,7 @@ import { TwoFactorAuthenticationPolicyComponent } from "./two-factor-authenticat PoliciesComponent, PolicyEditComponent, RemoveUnlockWithPinPolicyComponent, + RestrictedItemTypesPolicyComponent, ], exports: [ DisableSendPolicyComponent, diff --git a/apps/web/src/app/admin-console/organizations/policies/policy-edit.component.html b/apps/web/src/app/admin-console/organizations/policies/policy-edit.component.html index 671083a2318..7f33f08f888 100644 --- a/apps/web/src/app/admin-console/organizations/policies/policy-edit.component.html +++ b/apps/web/src/app/admin-console/organizations/policies/policy-edit.component.html @@ -1,5 +1,17 @@
+ + +
+ + + diff --git a/apps/web/src/app/admin-console/organizations/policies/policy-edit.component.ts b/apps/web/src/app/admin-console/organizations/policies/policy-edit.component.ts index f33460e8c16..d3d03d2aaae 100644 --- a/apps/web/src/app/admin-console/organizations/policies/policy-edit.component.ts +++ b/apps/web/src/app/admin-console/organizations/policies/policy-edit.component.ts @@ -9,12 +9,20 @@ import { ViewContainerRef, } from "@angular/core"; import { FormBuilder } from "@angular/forms"; -import { Observable, map } from "rxjs"; +import { map, Observable, switchMap } from "rxjs"; +import { + getOrganizationById, + OrganizationService, +} from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction"; import { PolicyApiServiceAbstraction } from "@bitwarden/common/admin-console/abstractions/policy/policy-api.service.abstraction"; import { PolicyType } from "@bitwarden/common/admin-console/enums"; +import { Organization } from "@bitwarden/common/admin-console/models/domain/organization"; import { PolicyRequest } from "@bitwarden/common/admin-console/models/request/policy.request"; import { PolicyResponse } from "@bitwarden/common/admin-console/models/response/policy.response"; +import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; +import { getUserId } from "@bitwarden/common/auth/services/account.service"; +import { OrganizationBillingServiceAbstraction } from "@bitwarden/common/billing/abstractions"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { DIALOG_DATA, @@ -33,12 +41,16 @@ export type PolicyEditDialogData = { organizationId: string; }; +// FIXME: update to use a const object instead of a typescript enum +// eslint-disable-next-line @bitwarden/platform/no-enums export enum PolicyEditDialogResult { Saved = "saved", + UpgradePlan = "upgrade-plan", } @Component({ selector: "app-policy-edit", templateUrl: "policy-edit.component.html", + standalone: false, }) export class PolicyEditComponent implements AfterViewInit { @ViewChild("policyForm", { read: ViewContainerRef, static: true }) @@ -48,22 +60,28 @@ export class PolicyEditComponent implements AfterViewInit { loading = true; enabled = false; saveDisabled$: Observable; - defaultTypes: any[]; policyComponent: BasePolicyComponent; private policyResponse: PolicyResponse; formGroup = this.formBuilder.group({ enabled: [this.enabled], }); + protected organization$: Observable; + protected isBreadcrumbingEnabled$: Observable; + constructor( @Inject(DIALOG_DATA) protected data: PolicyEditDialogData, + private accountService: AccountService, private policyApiService: PolicyApiServiceAbstraction, + private organizationService: OrganizationService, private i18nService: I18nService, private cdr: ChangeDetectorRef, private formBuilder: FormBuilder, private dialogRef: DialogRef, private toastService: ToastService, + private organizationBillingService: OrganizationBillingServiceAbstraction, ) {} + get policy(): BasePolicy { return this.data.policy; } @@ -97,6 +115,16 @@ export class PolicyEditComponent implements AfterViewInit { throw e; } } + this.organization$ = this.accountService.activeAccount$.pipe( + getUserId, + switchMap((userId) => this.organizationService.organizations$(userId)), + getOrganizationById(this.data.organizationId), + ); + this.isBreadcrumbingEnabled$ = this.organization$.pipe( + switchMap((organization) => + this.organizationBillingService.isBreadcrumbingPoliciesEnabled$(organization), + ), + ); } submit = async () => { @@ -119,4 +147,8 @@ export class PolicyEditComponent implements AfterViewInit { static open = (dialogService: DialogService, config: DialogConfig) => { return dialogService.open(PolicyEditComponent, config); }; + + protected upgradePlan(): void { + this.dialogRef.close(PolicyEditDialogResult.UpgradePlan); + } } diff --git a/apps/web/src/app/admin-console/organizations/policies/remove-unlock-with-pin.component.ts b/apps/web/src/app/admin-console/organizations/policies/remove-unlock-with-pin.component.ts index b737c803b5f..0d0f42b603e 100644 --- a/apps/web/src/app/admin-console/organizations/policies/remove-unlock-with-pin.component.ts +++ b/apps/web/src/app/admin-console/organizations/policies/remove-unlock-with-pin.component.ts @@ -14,5 +14,6 @@ export class RemoveUnlockWithPinPolicy extends BasePolicy { @Component({ selector: "remove-unlock-with-pin", templateUrl: "remove-unlock-with-pin.component.html", + standalone: false, }) export class RemoveUnlockWithPinPolicyComponent extends BasePolicyComponent {} diff --git a/apps/web/src/app/admin-console/organizations/policies/require-sso.component.ts b/apps/web/src/app/admin-console/organizations/policies/require-sso.component.ts index ea85168f986..21de143dea6 100644 --- a/apps/web/src/app/admin-console/organizations/policies/require-sso.component.ts +++ b/apps/web/src/app/admin-console/organizations/policies/require-sso.component.ts @@ -19,5 +19,6 @@ export class RequireSsoPolicy extends BasePolicy { @Component({ selector: "policy-require-sso", templateUrl: "require-sso.component.html", + standalone: false, }) export class RequireSsoPolicyComponent extends BasePolicyComponent {} diff --git a/apps/web/src/app/admin-console/organizations/policies/reset-password.component.ts b/apps/web/src/app/admin-console/organizations/policies/reset-password.component.ts index 80e4e5254ff..62fc42f6a06 100644 --- a/apps/web/src/app/admin-console/organizations/policies/reset-password.component.ts +++ b/apps/web/src/app/admin-console/organizations/policies/reset-password.component.ts @@ -27,6 +27,7 @@ export class ResetPasswordPolicy extends BasePolicy { @Component({ selector: "policy-reset-password", templateUrl: "reset-password.component.html", + standalone: false, }) export class ResetPasswordPolicyComponent extends BasePolicyComponent implements OnInit { data = this.formBuilder.group({ diff --git a/apps/web/src/app/admin-console/organizations/policies/restricted-item-types.component.html b/apps/web/src/app/admin-console/organizations/policies/restricted-item-types.component.html new file mode 100644 index 00000000000..8665cc1fa6a --- /dev/null +++ b/apps/web/src/app/admin-console/organizations/policies/restricted-item-types.component.html @@ -0,0 +1,6 @@ + + + {{ "turnOn" | i18n }} + + diff --git a/apps/web/src/app/admin-console/organizations/policies/restricted-item-types.component.ts b/apps/web/src/app/admin-console/organizations/policies/restricted-item-types.component.ts new file mode 100644 index 00000000000..406014973f0 --- /dev/null +++ b/apps/web/src/app/admin-console/organizations/policies/restricted-item-types.component.ts @@ -0,0 +1,23 @@ +import { Component } from "@angular/core"; + +import { PolicyType } from "@bitwarden/common/admin-console/enums"; + +import { BasePolicy, BasePolicyComponent } from "./base-policy.component"; + +export class RestrictedItemTypesPolicy extends BasePolicy { + name = "restrictedItemTypesPolicy"; + description = "restrictedItemTypesPolicyDesc"; + type = PolicyType.RestrictedItemTypes; + component = RestrictedItemTypesPolicyComponent; +} + +@Component({ + selector: "policy-restricted-item-types", + templateUrl: "restricted-item-types.component.html", + standalone: false, +}) +export class RestrictedItemTypesPolicyComponent extends BasePolicyComponent { + constructor() { + super(); + } +} diff --git a/apps/web/src/app/admin-console/organizations/policies/send-options.component.ts b/apps/web/src/app/admin-console/organizations/policies/send-options.component.ts index af6d1f38694..9a0a8871296 100644 --- a/apps/web/src/app/admin-console/organizations/policies/send-options.component.ts +++ b/apps/web/src/app/admin-console/organizations/policies/send-options.component.ts @@ -15,6 +15,7 @@ export class SendOptionsPolicy extends BasePolicy { @Component({ selector: "policy-send-options", templateUrl: "send-options.component.html", + standalone: false, }) export class SendOptionsPolicyComponent extends BasePolicyComponent { data = this.formBuilder.group({ diff --git a/apps/web/src/app/admin-console/organizations/policies/single-org.component.html b/apps/web/src/app/admin-console/organizations/policies/single-org.component.html index db48c835750..082970a4f05 100644 --- a/apps/web/src/app/admin-console/organizations/policies/single-org.component.html +++ b/apps/web/src/app/admin-console/organizations/policies/single-org.component.html @@ -1,11 +1,6 @@ - + {{ "singleOrgPolicyMemberWarning" | i18n }} - - - {{ "singleOrgPolicyWarning" | i18n }} - - diff --git a/apps/web/src/app/admin-console/organizations/policies/single-org.component.ts b/apps/web/src/app/admin-console/organizations/policies/single-org.component.ts index b3a537f08da..ad32b4218bc 100644 --- a/apps/web/src/app/admin-console/organizations/policies/single-org.component.ts +++ b/apps/web/src/app/admin-console/organizations/policies/single-org.component.ts @@ -1,15 +1,12 @@ import { Component, OnInit } from "@angular/core"; -import { firstValueFrom, Observable } from "rxjs"; import { PolicyType } from "@bitwarden/common/admin-console/enums"; -import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum"; -import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; import { BasePolicy, BasePolicyComponent } from "./base-policy.component"; export class SingleOrgPolicy extends BasePolicy { name = "singleOrg"; - description = "singleOrgDesc"; + description = "singleOrgPolicyDesc"; type = PolicyType.SingleOrg; component = SingleOrgPolicyComponent; } @@ -17,24 +14,12 @@ export class SingleOrgPolicy extends BasePolicy { @Component({ selector: "policy-single-org", templateUrl: "single-org.component.html", + standalone: false, }) export class SingleOrgPolicyComponent extends BasePolicyComponent implements OnInit { - constructor(private configService: ConfigService) { - super(); - } - - protected accountDeprovisioningEnabled$: Observable = this.configService.getFeatureFlag$( - FeatureFlag.AccountDeprovisioning, - ); - async ngOnInit() { super.ngOnInit(); - const isAccountDeprovisioningEnabled = await firstValueFrom(this.accountDeprovisioningEnabled$); - this.policy.description = isAccountDeprovisioningEnabled - ? "singleOrgPolicyDesc" - : "singleOrgDesc"; - if (!this.policyResponse.canToggleState) { this.enabled.disable(); } diff --git a/apps/web/src/app/admin-console/organizations/policies/two-factor-authentication.component.ts b/apps/web/src/app/admin-console/organizations/policies/two-factor-authentication.component.ts index 51151808872..691e12c72f6 100644 --- a/apps/web/src/app/admin-console/organizations/policies/two-factor-authentication.component.ts +++ b/apps/web/src/app/admin-console/organizations/policies/two-factor-authentication.component.ts @@ -14,5 +14,6 @@ export class TwoFactorAuthenticationPolicy extends BasePolicy { @Component({ selector: "policy-two-factor-authentication", templateUrl: "two-factor-authentication.component.html", + standalone: false, }) export class TwoFactorAuthenticationPolicyComponent extends BasePolicyComponent {} diff --git a/apps/web/src/app/admin-console/organizations/reporting/organization-reporting-routing.module.ts b/apps/web/src/app/admin-console/organizations/reporting/organization-reporting-routing.module.ts index 635053dd1e2..4c825b26bb2 100644 --- a/apps/web/src/app/admin-console/organizations/reporting/organization-reporting-routing.module.ts +++ b/apps/web/src/app/admin-console/organizations/reporting/organization-reporting-routing.module.ts @@ -9,12 +9,16 @@ import { Organization } from "@bitwarden/common/admin-console/models/domain/orga import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum"; import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; -import { ExposedPasswordsReportComponent } from "../../../tools/reports/pages/organizations/exposed-passwords-report.component"; -import { InactiveTwoFactorReportComponent } from "../../../tools/reports/pages/organizations/inactive-two-factor-report.component"; -import { ReusedPasswordsReportComponent } from "../../../tools/reports/pages/organizations/reused-passwords-report.component"; -import { UnsecuredWebsitesReportComponent } from "../../../tools/reports/pages/organizations/unsecured-websites-report.component"; -import { WeakPasswordsReportComponent } from "../../../tools/reports/pages/organizations/weak-passwords-report.component"; -/* eslint no-restricted-imports: "error" */ +// eslint-disable-next-line no-restricted-imports +import { ExposedPasswordsReportComponent } from "../../../dirt/reports/pages/organizations/exposed-passwords-report.component"; +// eslint-disable-next-line no-restricted-imports +import { InactiveTwoFactorReportComponent } from "../../../dirt/reports/pages/organizations/inactive-two-factor-report.component"; +// eslint-disable-next-line no-restricted-imports +import { ReusedPasswordsReportComponent } from "../../../dirt/reports/pages/organizations/reused-passwords-report.component"; +// eslint-disable-next-line no-restricted-imports +import { UnsecuredWebsitesReportComponent } from "../../../dirt/reports/pages/organizations/unsecured-websites-report.component"; +// eslint-disable-next-line no-restricted-imports +import { WeakPasswordsReportComponent } from "../../../dirt/reports/pages/organizations/weak-passwords-report.component"; import { isPaidOrgGuard } from "../guards/is-paid-org.guard"; import { organizationPermissionsGuard } from "../guards/org-permissions.guard"; import { organizationRedirectGuard } from "../guards/org-redirect.guard"; diff --git a/apps/web/src/app/admin-console/organizations/reporting/organization-reporting.module.ts b/apps/web/src/app/admin-console/organizations/reporting/organization-reporting.module.ts index d17bb0c5995..22c3edcf240 100644 --- a/apps/web/src/app/admin-console/organizations/reporting/organization-reporting.module.ts +++ b/apps/web/src/app/admin-console/organizations/reporting/organization-reporting.module.ts @@ -1,8 +1,8 @@ import { NgModule } from "@angular/core"; +import { ReportsSharedModule } from "../../../dirt/reports"; import { LooseComponentsModule } from "../../../shared"; import { SharedModule } from "../../../shared/shared.module"; -import { ReportsSharedModule } from "../../../tools/reports"; import { OrganizationReportingRoutingModule } from "./organization-reporting-routing.module"; import { ReportsHomeComponent } from "./reports-home.component"; diff --git a/apps/web/src/app/admin-console/organizations/reporting/reports-home.component.ts b/apps/web/src/app/admin-console/organizations/reporting/reports-home.component.ts index 6090396293f..52cb24c90d1 100644 --- a/apps/web/src/app/admin-console/organizations/reporting/reports-home.component.ts +++ b/apps/web/src/app/admin-console/organizations/reporting/reports-home.component.ts @@ -12,11 +12,12 @@ import { AccountService } from "@bitwarden/common/auth/abstractions/account.serv import { getUserId } from "@bitwarden/common/auth/services/account.service"; import { ProductTierType } from "@bitwarden/common/billing/enums"; -import { ReportVariant, reports, ReportType, ReportEntry } from "../../../tools/reports"; +import { ReportVariant, reports, ReportType, ReportEntry } from "../../../dirt/reports"; @Component({ selector: "app-org-reports-home", templateUrl: "reports-home.component.html", + standalone: false, }) export class ReportsHomeComponent implements OnInit { reports$: Observable; diff --git a/apps/web/src/app/admin-console/organizations/settings/account.component.html b/apps/web/src/app/admin-console/organizations/settings/account.component.html index 8ae94b08f57..4ce4398aadc 100644 --- a/apps/web/src/app/admin-console/organizations/settings/account.component.html +++ b/apps/web/src/app/admin-console/organizations/settings/account.component.html @@ -70,7 +70,7 @@ {{ "limitCollectionDeletionDesc" | i18n }} - + {{ "limitItemDeletionDescription" | i18n }} @@ -93,7 +93,4 @@ {{ "purgeVault" | i18n }} - - - diff --git a/apps/web/src/app/admin-console/organizations/settings/account.component.ts b/apps/web/src/app/admin-console/organizations/settings/account.component.ts index 57892442c16..ecb2dbc54a2 100644 --- a/apps/web/src/app/admin-console/organizations/settings/account.component.ts +++ b/apps/web/src/app/admin-console/organizations/settings/account.component.ts @@ -1,6 +1,6 @@ // FIXME: Update this file to be type safe and remove this and next line // @ts-strict-ignore -import { Component, OnDestroy, OnInit, ViewChild, ViewContainerRef } from "@angular/core"; +import { Component, OnDestroy, OnInit } from "@angular/core"; import { FormBuilder, Validators } from "@angular/forms"; import { ActivatedRoute, Router } from "@angular/router"; import { @@ -25,8 +25,6 @@ import { OrganizationUpdateRequest } from "@bitwarden/common/admin-console/model import { OrganizationResponse } from "@bitwarden/common/admin-console/models/response/organization.response"; 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 { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; import { Utils } from "@bitwarden/common/platform/misc/utils"; @@ -41,13 +39,9 @@ import { DeleteOrganizationDialogResult, openDeleteOrganizationDialog } from "./ @Component({ selector: "app-org-account", templateUrl: "account.component.html", + standalone: false, }) export class AccountComponent implements OnInit, OnDestroy { - @ViewChild("apiKeyTemplate", { read: ViewContainerRef, static: true }) - apiKeyModalRef: ViewContainerRef; - @ViewChild("rotateApiKeyTemplate", { read: ViewContainerRef, static: true }) - rotateApiKeyModalRef: ViewContainerRef; - selfHosted = false; canEditSubscription = true; loading = true; @@ -55,8 +49,6 @@ export class AccountComponent implements OnInit, OnDestroy { org: OrganizationResponse; taxFormPromise: Promise; - limitItemDeletionFeatureFlagIsEnabled: boolean; - // FormGroup validators taken from server Organization domain object protected formGroup = this.formBuilder.group({ orgName: this.formBuilder.control( @@ -99,17 +91,11 @@ export class AccountComponent implements OnInit, OnDestroy { private dialogService: DialogService, private formBuilder: FormBuilder, private toastService: ToastService, - private configService: ConfigService, ) {} async ngOnInit() { this.selfHosted = this.platformUtilsService.isSelfHost(); - this.configService - .getFeatureFlag$(FeatureFlag.LimitItemDeletion) - .pipe(takeUntil(this.destroy$)) - .subscribe((isAble) => (this.limitItemDeletionFeatureFlagIsEnabled = isAble)); - const userId = await firstValueFrom(getUserId(this.accountService.activeAccount$)); this.route.params .pipe( diff --git a/apps/web/src/app/admin-console/organizations/settings/components/delete-organization-dialog.component.ts b/apps/web/src/app/admin-console/organizations/settings/components/delete-organization-dialog.component.ts index c23dcf2c8f2..1b41dc31a62 100644 --- a/apps/web/src/app/admin-console/organizations/settings/components/delete-organization-dialog.component.ts +++ b/apps/web/src/app/admin-console/organizations/settings/components/delete-organization-dialog.component.ts @@ -18,7 +18,7 @@ import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.servic import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; import { Utils } from "@bitwarden/common/platform/misc/utils"; import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service"; -import { CipherType } from "@bitwarden/common/vault/enums"; +import { CipherType, toCipherTypeName } from "@bitwarden/common/vault/enums"; import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view"; import { DIALOG_DATA, @@ -71,6 +71,8 @@ export interface DeleteOrganizationDialogParams { requestType: "InvalidFamiliesForEnterprise" | "RegularDelete"; } +// FIXME: update to use a const object instead of a typescript enum +// eslint-disable-next-line @bitwarden/platform/no-enums export enum DeleteOrganizationDialogResult { Deleted = "deleted", Canceled = "canceled", @@ -78,7 +80,6 @@ export enum DeleteOrganizationDialogResult { @Component({ selector: "app-delete-organization", - standalone: true, imports: [SharedModule, UserVerificationModule], templateUrl: "delete-organization-dialog.component.html", }) @@ -161,7 +162,7 @@ export class DeleteOrganizationDialogComponent implements OnInit, OnDestroy { organizationContentSummary.itemCountByType.push( new OrganizationContentSummaryItem( count, - this.getOrganizationItemLocalizationKeysByType(CipherType[cipherType]), + this.getOrganizationItemLocalizationKeysByType(toCipherTypeName(cipherType)), ), ); } diff --git a/apps/web/src/app/admin-console/organizations/settings/organization-settings-routing.module.ts b/apps/web/src/app/admin-console/organizations/settings/organization-settings-routing.module.ts index a644086628c..cfec0be531b 100644 --- a/apps/web/src/app/admin-console/organizations/settings/organization-settings-routing.module.ts +++ b/apps/web/src/app/admin-console/organizations/settings/organization-settings-routing.module.ts @@ -1,8 +1,10 @@ -import { NgModule } from "@angular/core"; +import { NgModule, inject } from "@angular/core"; import { RouterModule, Routes } from "@angular/router"; +import { map } from "rxjs"; import { canAccessSettingsTab } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction"; import { Organization } from "@bitwarden/common/admin-console/models/domain/organization"; +import { OrganizationBillingServiceAbstraction } from "@bitwarden/common/billing/abstractions"; import { organizationPermissionsGuard } from "../../organizations/guards/org-permissions.guard"; import { organizationRedirectGuard } from "../../organizations/guards/org-redirect.guard"; @@ -41,7 +43,14 @@ const routes: Routes = [ { path: "policies", component: PoliciesComponent, - canActivate: [organizationPermissionsGuard((org) => org.canManagePolicies)], + canActivate: [ + organizationPermissionsGuard((o: Organization) => { + const organizationBillingService = inject(OrganizationBillingServiceAbstraction); + return organizationBillingService + .isBreadcrumbingPoliciesEnabled$(o) + .pipe(map((isBreadcrumbingEnabled) => o.canManagePolicies || isBreadcrumbingEnabled)); + }), + ], data: { titleId: "policies", }, diff --git a/apps/web/src/app/admin-console/organizations/settings/two-factor-setup.component.ts b/apps/web/src/app/admin-console/organizations/settings/two-factor-setup.component.ts index 014b8e3a3ef..020a16dd932 100644 --- a/apps/web/src/app/admin-console/organizations/settings/two-factor-setup.component.ts +++ b/apps/web/src/app/admin-console/organizations/settings/two-factor-setup.component.ts @@ -29,6 +29,7 @@ import { TwoFactorVerifyComponent } from "../../../auth/settings/two-factor/two- @Component({ selector: "app-two-factor-setup", templateUrl: "../../../auth/settings/two-factor/two-factor-setup.component.html", + standalone: false, }) export class TwoFactorSetupComponent extends BaseTwoFactorSetupComponent implements OnInit { tabbedHeader = false; @@ -118,7 +119,7 @@ export class TwoFactorSetupComponent extends BaseTwoFactorSetupComponent impleme return this.apiService.getTwoFactorOrganizationProviders(this.organizationId); } - protected filterProvider(type: TwoFactorProviderType) { + protected filterProvider(type: TwoFactorProviderType): boolean { return type !== TwoFactorProviderType.OrganizationDuo; } } diff --git a/apps/web/src/app/admin-console/organizations/shared/components/access-selector/access-selector.component.ts b/apps/web/src/app/admin-console/organizations/shared/components/access-selector/access-selector.component.ts index bb25cd90875..366df34b2b8 100644 --- a/apps/web/src/app/admin-console/organizations/shared/components/access-selector/access-selector.component.ts +++ b/apps/web/src/app/admin-console/organizations/shared/components/access-selector/access-selector.component.ts @@ -26,6 +26,8 @@ import { Permission, } from "./access-selector.models"; +// FIXME: update to use a const object instead of a typescript enum +// eslint-disable-next-line @bitwarden/platform/no-enums export enum PermissionMode { /** * No permission controls or column present. No permission values are emitted. @@ -53,6 +55,7 @@ export enum PermissionMode { multi: true, }, ], + standalone: false, }) export class AccessSelectorComponent implements ControlValueAccessor, OnInit, OnDestroy { private destroy$ = new Subject(); @@ -310,7 +313,7 @@ export class AccessSelectorComponent implements ControlValueAccessor, OnInit, On protected itemIcon(item: AccessItemView) { switch (item.type) { case AccessItemType.Collection: - return "bwi-collection"; + return "bwi-collection-shared"; case AccessItemType.Group: return "bwi-users"; case AccessItemType.Member: diff --git a/apps/web/src/app/admin-console/organizations/shared/components/access-selector/access-selector.models.ts b/apps/web/src/app/admin-console/organizations/shared/components/access-selector/access-selector.models.ts index 8702c0f7a6c..884483d32b0 100644 --- a/apps/web/src/app/admin-console/organizations/shared/components/access-selector/access-selector.models.ts +++ b/apps/web/src/app/admin-console/organizations/shared/components/access-selector/access-selector.models.ts @@ -15,6 +15,8 @@ import { GroupView } from "../../../core"; /** * Permission options that replace/correspond with manage, readOnly, and hidePassword server fields. */ +// FIXME: update to use a const object instead of a typescript enum +// eslint-disable-next-line @bitwarden/platform/no-enums export enum CollectionPermission { View = "view", ViewExceptPass = "viewExceptPass", @@ -23,6 +25,8 @@ export enum CollectionPermission { Manage = "manage", } +// FIXME: update to use a const object instead of a typescript enum +// eslint-disable-next-line @bitwarden/platform/no-enums export enum AccessItemType { Collection, Group, diff --git a/apps/web/src/app/admin-console/organizations/shared/components/access-selector/user-type.pipe.ts b/apps/web/src/app/admin-console/organizations/shared/components/access-selector/user-type.pipe.ts index 3d43c63efb0..673d09ec0f0 100644 --- a/apps/web/src/app/admin-console/organizations/shared/components/access-selector/user-type.pipe.ts +++ b/apps/web/src/app/admin-console/organizations/shared/components/access-selector/user-type.pipe.ts @@ -5,6 +5,7 @@ import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.servic @Pipe({ name: "userType", + standalone: false, }) export class UserTypePipe implements PipeTransform { constructor(private i18nService: I18nService) {} diff --git a/apps/web/src/app/admin-console/organizations/shared/components/collection-dialog/collection-dialog.component.html b/apps/web/src/app/admin-console/organizations/shared/components/collection-dialog/collection-dialog.component.html index 984d21bf251..4a91fcc2a41 100644 --- a/apps/web/src/app/admin-console/organizations/shared/components/collection-dialog/collection-dialog.component.html +++ b/apps/web/src/app/admin-console/organizations/shared/components/collection-dialog/collection-dialog.component.html @@ -35,7 +35,7 @@ - + {{ "externalId" | i18n }} {{ "externalIdDesc" | i18n }} @@ -48,14 +48,14 @@ diff --git a/apps/web/src/app/admin-console/organizations/shared/components/collection-dialog/collection-dialog.component.ts b/apps/web/src/app/admin-console/organizations/shared/components/collection-dialog/collection-dialog.component.ts index 37d0ebbd195..e9865f14d54 100644 --- a/apps/web/src/app/admin-console/organizations/shared/components/collection-dialog/collection-dialog.component.ts +++ b/apps/web/src/app/admin-console/organizations/shared/components/collection-dialog/collection-dialog.component.ts @@ -38,7 +38,6 @@ 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 { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; -import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; import { Utils } from "@bitwarden/common/platform/misc/utils"; import { DIALOG_DATA, @@ -65,6 +64,8 @@ import { } from "../access-selector/access-selector.models"; import { AccessSelectorModule } from "../access-selector/access-selector.module"; +// FIXME: update to use a const object instead of a typescript enum +// eslint-disable-next-line @bitwarden/platform/no-enums export enum CollectionDialogTabType { Info = 0, Access = 1, @@ -76,6 +77,8 @@ export enum CollectionDialogTabType { * @readonly * @enum {string} */ +// FIXME: update to use a const object instead of a typescript enum +// eslint-disable-next-line @bitwarden/platform/no-enums enum ButtonType { /** Displayed when the user has reached the maximum number of collections allowed for the organization. */ Upgrade = "upgrade", @@ -103,6 +106,8 @@ export interface CollectionDialogResult { collection: CollectionResponse | CollectionView; } +// FIXME: update to use a const object instead of a typescript enum +// eslint-disable-next-line @bitwarden/platform/no-enums export enum CollectionDialogAction { Saved = "saved", Canceled = "canceled", @@ -112,7 +117,6 @@ export enum CollectionDialogAction { @Component({ templateUrl: "collection-dialog.component.html", - standalone: true, imports: [SharedModule, AccessSelectorModule, SelectModule], }) export class CollectionDialogComponent implements OnInit, OnDestroy { @@ -129,7 +133,7 @@ export class CollectionDialogComponent implements OnInit, OnDestroy { protected showOrgSelector = false; protected formGroup = this.formBuilder.group({ name: ["", [Validators.required, BitValidators.forbiddenCharacters(["/"])]], - externalId: "", + externalId: { value: "", disabled: true }, parent: undefined as string | undefined, access: [[] as AccessItemValue[]], selectedOrg: "", @@ -139,16 +143,6 @@ export class CollectionDialogComponent implements OnInit, OnDestroy { protected showAddAccessWarning = false; protected collections: Collection[]; protected buttonDisplayName: ButtonType = ButtonType.Save; - protected isExternalIdVisible$ = this.configService - .getFeatureFlag$(FeatureFlag.SsoExternalIdVisibility) - .pipe( - map((isEnabled) => { - return ( - !isEnabled || - (!!this.params.isAdminConsoleActive && !!this.formGroup.get("externalId")?.value) - ); - }), - ); private orgExceedingCollectionLimit!: Organization; constructor( @@ -159,7 +153,6 @@ export class CollectionDialogComponent implements OnInit, OnDestroy { private groupService: GroupApiService, private collectionAdminService: CollectionAdminService, private i18nService: I18nService, - private platformUtilsService: PlatformUtilsService, private organizationUserApiService: OrganizationUserApiService, private dialogService: DialogService, private changeDetectorRef: ChangeDetectorRef, @@ -348,6 +341,10 @@ export class CollectionDialogComponent implements OnInit, OnDestroy { return this.formGroup.controls.selectedOrg; } + protected get isExternalIdVisible(): boolean { + return this.params.isAdminConsoleActive && !!this.formGroup.get("externalId")?.value; + } + protected get collectionId() { return this.params.collectionId; } @@ -484,23 +481,10 @@ export class CollectionDialogComponent implements OnInit, OnDestroy { private handleFormGroupReadonly(readonly: boolean) { if (readonly) { this.formGroup.controls.name.disable(); - this.formGroup.controls.externalId.disable(); this.formGroup.controls.parent.disable(); this.formGroup.controls.access.disable(); } else { this.formGroup.controls.name.enable(); - - this.configService - .getFeatureFlag$(FeatureFlag.SsoExternalIdVisibility) - .pipe(takeUntil(this.destroy$)) - .subscribe((isEnabled) => { - if (isEnabled) { - this.formGroup.controls.externalId.disable(); - } else { - this.formGroup.controls.externalId.enable(); - } - }); - this.formGroup.controls.parent.enable(); this.formGroup.controls.access.enable(); } diff --git a/apps/web/src/app/admin-console/organizations/shared/components/integrations/integration-card/integration-card.component.ts b/apps/web/src/app/admin-console/organizations/shared/components/integrations/integration-card/integration-card.component.ts index 3943ceb22ed..20e4028e9df 100644 --- a/apps/web/src/app/admin-console/organizations/shared/components/integrations/integration-card/integration-card.component.ts +++ b/apps/web/src/app/admin-console/organizations/shared/components/integrations/integration-card/integration-card.component.ts @@ -20,7 +20,6 @@ import { SharedModule } from "../../../../../../shared/shared.module"; @Component({ selector: "app-integration-card", templateUrl: "./integration-card.component.html", - standalone: true, imports: [SharedModule], }) export class IntegrationCardComponent implements AfterViewInit, OnDestroy { diff --git a/apps/web/src/app/admin-console/organizations/shared/components/integrations/integration-grid/integration-grid.component.ts b/apps/web/src/app/admin-console/organizations/shared/components/integrations/integration-grid/integration-grid.component.ts index 2e3158f9894..55b552bd251 100644 --- a/apps/web/src/app/admin-console/organizations/shared/components/integrations/integration-grid/integration-grid.component.ts +++ b/apps/web/src/app/admin-console/organizations/shared/components/integrations/integration-grid/integration-grid.component.ts @@ -11,7 +11,6 @@ import { Integration } from "../models"; @Component({ selector: "app-integration-grid", templateUrl: "./integration-grid.component.html", - standalone: true, imports: [IntegrationCardComponent, SharedModule], }) export class IntegrationGridComponent { diff --git a/apps/web/src/app/admin-console/organizations/shared/components/integrations/integrations.pipe.ts b/apps/web/src/app/admin-console/organizations/shared/components/integrations/integrations.pipe.ts index 760d9913e9e..ae9f73e78c0 100644 --- a/apps/web/src/app/admin-console/organizations/shared/components/integrations/integrations.pipe.ts +++ b/apps/web/src/app/admin-console/organizations/shared/components/integrations/integrations.pipe.ts @@ -6,7 +6,6 @@ import { Integration } from "../../../shared/components/integrations/models"; @Pipe({ name: "filterIntegrations", - standalone: true, }) export class FilterIntegrationsPipe implements PipeTransform { transform(integrations: Integration[], type: IntegrationType): Integration[] { diff --git a/apps/web/src/app/admin-console/organizations/sponsorships/accept-family-sponsorship.component.html b/apps/web/src/app/admin-console/organizations/sponsorships/accept-family-sponsorship.component.html index fdbb6dbba91..ca1264829b9 100644 --- a/apps/web/src/app/admin-console/organizations/sponsorships/accept-family-sponsorship.component.html +++ b/apps/web/src/app/admin-console/organizations/sponsorships/accept-family-sponsorship.component.html @@ -1,6 +1,7 @@
- + +
( secret, ); - const dialogRef = OrganizationTrustComponent.open(dialogService, { + const dialogRef = AccountRecoveryTrustComponent.open(dialogService, { name: data.organization.name, orgId: data.organization.id, publicKey, diff --git a/apps/web/src/app/admin-console/settings/create-organization.component.ts b/apps/web/src/app/admin-console/settings/create-organization.component.ts index 7a20826086d..f87e9ec5b72 100644 --- a/apps/web/src/app/admin-console/settings/create-organization.component.ts +++ b/apps/web/src/app/admin-console/settings/create-organization.component.ts @@ -13,7 +13,6 @@ import { SharedModule } from "../../shared"; @Component({ templateUrl: "create-organization.component.html", - standalone: true, imports: [SharedModule, OrganizationPlansComponent, HeaderModule], }) export class CreateOrganizationComponent { diff --git a/apps/web/src/app/app.component.ts b/apps/web/src/app/app.component.ts index 55e2595e0f7..15436f3097a 100644 --- a/apps/web/src/app/app.component.ts +++ b/apps/web/src/app/app.component.ts @@ -1,27 +1,22 @@ // FIXME: Update this file to be type safe and remove this and next line // @ts-strict-ignore -import { DOCUMENT } from "@angular/common"; -import { Component, Inject, NgZone, OnDestroy, OnInit } from "@angular/core"; +import { Component, DestroyRef, NgZone, OnDestroy, OnInit } from "@angular/core"; import { takeUntilDestroyed } from "@angular/core/rxjs-interop"; -import { NavigationEnd, Router } from "@angular/router"; -import * as jq from "jquery"; -import { Subject, filter, firstValueFrom, map, takeUntil, timeout } from "rxjs"; +import { Router } from "@angular/router"; +import { Subject, filter, firstValueFrom, map, timeout } from "rxjs"; import { CollectionService } from "@bitwarden/admin-console/common"; import { DeviceTrustToastService } from "@bitwarden/angular/auth/services/device-trust-toast.service.abstraction"; -import { ApiService } from "@bitwarden/common/abstractions/api.service"; +import { DocumentLangSetter } from "@bitwarden/angular/platform/i18n"; import { EventUploadService } from "@bitwarden/common/abstractions/event/event-upload.service"; import { SearchService } from "@bitwarden/common/abstractions/search.service"; import { InternalOrganizationServiceAbstraction } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction"; -import { InternalPolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction"; import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; import { AuthService } from "@bitwarden/common/auth/abstractions/auth.service"; import { AuthenticationStatus } from "@bitwarden/common/auth/enums/authentication-status"; import { getUserId } from "@bitwarden/common/auth/services/account.service"; import { ProcessReloadServiceAbstraction } from "@bitwarden/common/key-management/abstractions/process-reload.service"; -import { KeyConnectorService } from "@bitwarden/common/key-management/key-connector/abstractions/key-connector.service"; import { VaultTimeoutService } from "@bitwarden/common/key-management/vault-timeout"; -import { AppIdService } from "@bitwarden/common/platform/abstractions/app-id.service"; import { BroadcasterService } from "@bitwarden/common/platform/abstractions/broadcaster.service"; import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; @@ -29,11 +24,9 @@ import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/pl import { StateService } from "@bitwarden/common/platform/abstractions/state.service"; import { NotificationsService } from "@bitwarden/common/platform/notifications"; import { StateEventRunnerService } from "@bitwarden/common/platform/state"; -import { SyncService } from "@bitwarden/common/platform/sync"; import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service"; import { InternalFolderService } from "@bitwarden/common/vault/abstractions/folder/folder.service.abstraction"; import { DialogService, ToastService } from "@bitwarden/components"; -import { PasswordGenerationServiceAbstraction } from "@bitwarden/generator-legacy"; import { KeyService, BiometricStateService } from "@bitwarden/key-management"; import { PolicyListService } from "./admin-console/core/policy-list.service"; @@ -56,6 +49,7 @@ const IdleTimeout = 60000 * 10; // 10 minutes @Component({ selector: "app-root", templateUrl: "app.component.html", + standalone: false, }) export class AppComponent implements OnDestroy, OnInit { private lastActivity: Date = null; @@ -66,11 +60,8 @@ export class AppComponent implements OnDestroy, OnInit { loading = false; constructor( - @Inject(DOCUMENT) private document: Document, private broadcasterService: BroadcasterService, private folderService: InternalFolderService, - private syncService: SyncService, - private passwordGenerationService: PasswordGenerationServiceAbstraction, private cipherService: CipherService, private authService: AuthService, private router: Router, @@ -85,28 +76,25 @@ export class AppComponent implements OnDestroy, OnInit { private notificationsService: NotificationsService, private stateService: StateService, private eventUploadService: EventUploadService, - private policyService: InternalPolicyService, protected policyListService: PolicyListService, - private keyConnectorService: KeyConnectorService, protected configService: ConfigService, private dialogService: DialogService, private biometricStateService: BiometricStateService, private stateEventRunnerService: StateEventRunnerService, private organizationService: InternalOrganizationServiceAbstraction, private accountService: AccountService, - private apiService: ApiService, - private appIdService: AppIdService, private processReloadService: ProcessReloadServiceAbstraction, private deviceTrustToastService: DeviceTrustToastService, + private readonly destoryRef: DestroyRef, + private readonly documentLangSetter: DocumentLangSetter, ) { this.deviceTrustToastService.setupListeners$.pipe(takeUntilDestroyed()).subscribe(); + + const langSubscription = this.documentLangSetter.start(); + this.destoryRef.onDestroy(() => langSubscription.unsubscribe()); } ngOnInit() { - this.i18nService.locale$.pipe(takeUntil(this.destroy$)).subscribe((locale) => { - this.document.documentElement.lang = locale; - }); - this.ngZone.runOutsideAngular(() => { window.onmousemove = () => this.recordActivity(); window.onmousedown = () => this.recordActivity(); @@ -247,15 +235,6 @@ export class AppComponent implements OnDestroy, OnInit { }); }); - this.router.events.pipe(takeUntil(this.destroy$)).subscribe((event) => { - if (event instanceof NavigationEnd) { - const modals = Array.from(document.querySelectorAll(".modal")); - for (const modal of modals) { - (jq(modal) as any).modal("hide"); - } - } - }); - this.policyListService.addPolicies([ new TwoFactorAuthenticationPolicy(), new MasterPasswordPolicy(), @@ -303,7 +282,7 @@ export class AppComponent implements OnDestroy, OnInit { ); await Promise.all([ - this.keyService.clearKeys(), + this.keyService.clearKeys(userId), this.cipherService.clear(userId), this.folderService.clear(userId), this.collectionService.clear(userId), diff --git a/apps/web/src/app/auth/core/enums/webauthn-login-credential-prf-status.enum.ts b/apps/web/src/app/auth/core/enums/webauthn-login-credential-prf-status.enum.ts index 7dc8217fde5..3073917e57b 100644 --- a/apps/web/src/app/auth/core/enums/webauthn-login-credential-prf-status.enum.ts +++ b/apps/web/src/app/auth/core/enums/webauthn-login-credential-prf-status.enum.ts @@ -1,3 +1,5 @@ +// FIXME: update to use a const object instead of a typescript enum +// eslint-disable-next-line @bitwarden/platform/no-enums export enum WebauthnLoginCredentialPrfStatus { Enabled = 0, Supported = 1, diff --git a/apps/web/src/app/auth/core/services/change-password/index.ts b/apps/web/src/app/auth/core/services/change-password/index.ts new file mode 100644 index 00000000000..9b2aa1c0143 --- /dev/null +++ b/apps/web/src/app/auth/core/services/change-password/index.ts @@ -0,0 +1 @@ +export * from "./web-change-password.service"; diff --git a/apps/web/src/app/auth/core/services/change-password/web-change-password.service.spec.ts b/apps/web/src/app/auth/core/services/change-password/web-change-password.service.spec.ts new file mode 100644 index 00000000000..45abfe4720a --- /dev/null +++ b/apps/web/src/app/auth/core/services/change-password/web-change-password.service.spec.ts @@ -0,0 +1,63 @@ +import { mock, MockProxy } from "jest-mock-extended"; + +import { ChangePasswordService } from "@bitwarden/auth/angular"; +import { Account } from "@bitwarden/common/auth/abstractions/account.service"; +import { MasterPasswordApiService } from "@bitwarden/common/auth/abstractions/master-password-api.service.abstraction"; +import { InternalMasterPasswordServiceAbstraction } from "@bitwarden/common/key-management/master-password/abstractions/master-password.service.abstraction"; +import { UserId } from "@bitwarden/common/types/guid"; +import { KeyService } from "@bitwarden/key-management"; +import { UserKeyRotationService } from "@bitwarden/web-vault/app/key-management/key-rotation/user-key-rotation.service"; + +import { WebChangePasswordService } from "./web-change-password.service"; + +describe("WebChangePasswordService", () => { + let keyService: MockProxy; + let masterPasswordApiService: MockProxy; + let masterPasswordService: MockProxy; + let userKeyRotationService: MockProxy; + + let sut: ChangePasswordService; + + const userId = "userId" as UserId; + const user: Account = { + id: userId, + email: "email", + emailVerified: false, + name: "name", + }; + + const currentPassword = "currentPassword"; + const newPassword = "newPassword"; + const newPasswordHint = "newPasswordHint"; + + beforeEach(() => { + keyService = mock(); + masterPasswordApiService = mock(); + masterPasswordService = mock(); + userKeyRotationService = mock(); + + sut = new WebChangePasswordService( + keyService, + masterPasswordApiService, + masterPasswordService, + userKeyRotationService, + ); + }); + + describe("rotateUserKeyMasterPasswordAndEncryptedData()", () => { + it("should call the method with the same name on the UserKeyRotationService with the correct arguments", async () => { + // Arrange & Act + await sut.rotateUserKeyMasterPasswordAndEncryptedData( + currentPassword, + newPassword, + user, + newPasswordHint, + ); + + // Assert + expect( + userKeyRotationService.rotateUserKeyMasterPasswordAndEncryptedData, + ).toHaveBeenCalledWith(currentPassword, newPassword, user, newPasswordHint); + }); + }); +}); diff --git a/apps/web/src/app/auth/core/services/change-password/web-change-password.service.ts b/apps/web/src/app/auth/core/services/change-password/web-change-password.service.ts new file mode 100644 index 00000000000..b75aef0f1fc --- /dev/null +++ b/apps/web/src/app/auth/core/services/change-password/web-change-password.service.ts @@ -0,0 +1,34 @@ +import { ChangePasswordService, DefaultChangePasswordService } from "@bitwarden/auth/angular"; +import { Account } from "@bitwarden/common/auth/abstractions/account.service"; +import { MasterPasswordApiService } from "@bitwarden/common/auth/abstractions/master-password-api.service.abstraction"; +import { InternalMasterPasswordServiceAbstraction } from "@bitwarden/common/key-management/master-password/abstractions/master-password.service.abstraction"; +import { KeyService } from "@bitwarden/key-management"; +import { UserKeyRotationService } from "@bitwarden/web-vault/app/key-management/key-rotation/user-key-rotation.service"; + +export class WebChangePasswordService + extends DefaultChangePasswordService + implements ChangePasswordService +{ + constructor( + protected keyService: KeyService, + protected masterPasswordApiService: MasterPasswordApiService, + protected masterPasswordService: InternalMasterPasswordServiceAbstraction, + private userKeyRotationService: UserKeyRotationService, + ) { + super(keyService, masterPasswordApiService, masterPasswordService); + } + + override async rotateUserKeyMasterPasswordAndEncryptedData( + currentPassword: string, + newPassword: string, + user: Account, + newPasswordHint: string, + ): Promise { + await this.userKeyRotationService.rotateUserKeyMasterPasswordAndEncryptedData( + currentPassword, + newPassword, + user, + newPasswordHint, + ); + } +} diff --git a/apps/web/src/app/auth/core/services/index.ts b/apps/web/src/app/auth/core/services/index.ts index 11c8dd98872..5539e3b76ea 100644 --- a/apps/web/src/app/auth/core/services/index.ts +++ b/apps/web/src/app/auth/core/services/index.ts @@ -1,3 +1,4 @@ +export * from "./change-password"; export * from "./login"; export * from "./login-decryption-options"; export * from "./webauthn-login"; diff --git a/apps/web/src/app/auth/core/services/login/web-login-component.service.ts b/apps/web/src/app/auth/core/services/login/web-login-component.service.ts index c644f26dd90..36e7143ccd0 100644 --- a/apps/web/src/app/auth/core/services/login/web-login-component.service.ts +++ b/apps/web/src/app/auth/core/services/login/web-login-component.service.ts @@ -98,7 +98,7 @@ export class WebLoginComponentService const enforcedPasswordPolicyOptions = await firstValueFrom( this.accountService.activeAccount$.pipe( getUserId, - switchMap((userId) => this.policyService.masterPasswordPolicyOptions$(userId)), + switchMap((userId) => this.policyService.masterPasswordPolicyOptions$(userId, policies)), ), ); diff --git a/apps/web/src/app/auth/core/services/registration/web-registration-finish.service.spec.ts b/apps/web/src/app/auth/core/services/registration/web-registration-finish.service.spec.ts index aa02e28b3b3..fe3b0837aa8 100644 --- a/apps/web/src/app/auth/core/services/registration/web-registration-finish.service.spec.ts +++ b/apps/web/src/app/auth/core/services/registration/web-registration-finish.service.spec.ts @@ -172,7 +172,6 @@ describe("WebRegistrationFinishService", () => { let userKey: UserKey; let userKeyEncString: EncString; let userKeyPair: [string, EncString]; - let capchaBypassToken: string; let orgInvite: OrganizationInvite; let orgSponsoredFreeFamilyPlanToken: string; @@ -186,11 +185,11 @@ describe("WebRegistrationFinishService", () => { emailVerificationToken = "emailVerificationToken"; masterKey = new SymmetricCryptoKey(new Uint8Array(64).buffer as CsprngArray) as MasterKey; passwordInputResult = { - masterKey: masterKey, - serverMasterKeyHash: "serverMasterKeyHash", - localMasterKeyHash: "localMasterKeyHash", + newMasterKey: masterKey, + newServerMasterKeyHash: "newServerMasterKeyHash", + newLocalMasterKeyHash: "newLocalMasterKeyHash", kdfConfig: DEFAULT_KDF_CONFIG, - hint: "hint", + newPasswordHint: "newPasswordHint", newPassword: "newPassword", }; @@ -198,7 +197,6 @@ describe("WebRegistrationFinishService", () => { userKeyEncString = new EncString("userKeyEncrypted"); userKeyPair = ["publicKey", new EncString("privateKey")]; - capchaBypassToken = "capchaBypassToken"; orgInvite = new OrganizationInvite(); orgInvite.organizationUserId = "organizationUserId"; @@ -219,19 +217,13 @@ describe("WebRegistrationFinishService", () => { ); }); - it("registers the user and returns a captcha bypass token when given valid email verification input", async () => { + it("registers the user when given valid email verification input", async () => { keyService.makeUserKey.mockResolvedValue([userKey, userKeyEncString]); keyService.makeKeyPair.mockResolvedValue(userKeyPair); - accountApiService.registerFinish.mockResolvedValue(capchaBypassToken); + accountApiService.registerFinish.mockResolvedValue(); acceptOrgInviteService.getOrganizationInvite.mockResolvedValue(null); - const result = await service.finishRegistration( - email, - passwordInputResult, - emailVerificationToken, - ); - - expect(result).toEqual(capchaBypassToken); + await service.finishRegistration(email, passwordInputResult, emailVerificationToken); expect(keyService.makeUserKey).toHaveBeenCalledWith(masterKey); expect(keyService.makeKeyPair).toHaveBeenCalledWith(userKey); @@ -239,8 +231,8 @@ describe("WebRegistrationFinishService", () => { expect.objectContaining({ email, emailVerificationToken: emailVerificationToken, - masterPasswordHash: passwordInputResult.serverMasterKeyHash, - masterPasswordHint: passwordInputResult.hint, + masterPasswordHash: passwordInputResult.newServerMasterKeyHash, + masterPasswordHint: passwordInputResult.newPasswordHint, userSymmetricKey: userKeyEncString.encryptedString, userAsymmetricKeys: { publicKey: userKeyPair[0], @@ -261,15 +253,13 @@ describe("WebRegistrationFinishService", () => { ); }); - it("it registers the user and returns a captcha bypass token when given an org invite", async () => { + it("it registers the user when given an org invite", async () => { keyService.makeUserKey.mockResolvedValue([userKey, userKeyEncString]); keyService.makeKeyPair.mockResolvedValue(userKeyPair); - accountApiService.registerFinish.mockResolvedValue(capchaBypassToken); + accountApiService.registerFinish.mockResolvedValue(); acceptOrgInviteService.getOrganizationInvite.mockResolvedValue(orgInvite); - const result = await service.finishRegistration(email, passwordInputResult); - - expect(result).toEqual(capchaBypassToken); + await service.finishRegistration(email, passwordInputResult); expect(keyService.makeUserKey).toHaveBeenCalledWith(masterKey); expect(keyService.makeKeyPair).toHaveBeenCalledWith(userKey); @@ -277,8 +267,8 @@ describe("WebRegistrationFinishService", () => { expect.objectContaining({ email, emailVerificationToken: undefined, - masterPasswordHash: passwordInputResult.serverMasterKeyHash, - masterPasswordHint: passwordInputResult.hint, + masterPasswordHash: passwordInputResult.newServerMasterKeyHash, + masterPasswordHint: passwordInputResult.newPasswordHint, userSymmetricKey: userKeyEncString.encryptedString, userAsymmetricKeys: { publicKey: userKeyPair[0], @@ -299,29 +289,27 @@ describe("WebRegistrationFinishService", () => { ); }); - it("registers the user and returns a captcha bypass token when given an org sponsored free family plan token", async () => { + it("registers the user when given an org sponsored free family plan token", async () => { keyService.makeUserKey.mockResolvedValue([userKey, userKeyEncString]); keyService.makeKeyPair.mockResolvedValue(userKeyPair); - accountApiService.registerFinish.mockResolvedValue(capchaBypassToken); + accountApiService.registerFinish.mockResolvedValue(); acceptOrgInviteService.getOrganizationInvite.mockResolvedValue(null); - const result = await service.finishRegistration( + await service.finishRegistration( email, passwordInputResult, undefined, orgSponsoredFreeFamilyPlanToken, ); - expect(result).toEqual(capchaBypassToken); - expect(keyService.makeUserKey).toHaveBeenCalledWith(masterKey); expect(keyService.makeKeyPair).toHaveBeenCalledWith(userKey); expect(accountApiService.registerFinish).toHaveBeenCalledWith( expect.objectContaining({ email, emailVerificationToken: undefined, - masterPasswordHash: passwordInputResult.serverMasterKeyHash, - masterPasswordHint: passwordInputResult.hint, + masterPasswordHash: passwordInputResult.newServerMasterKeyHash, + masterPasswordHint: passwordInputResult.newPasswordHint, userSymmetricKey: userKeyEncString.encryptedString, userAsymmetricKeys: { publicKey: userKeyPair[0], @@ -342,13 +330,13 @@ describe("WebRegistrationFinishService", () => { ); }); - it("registers the user and returns a captcha bypass token when given an emergency access invite token", async () => { + it("registers the user when given an emergency access invite token", async () => { keyService.makeUserKey.mockResolvedValue([userKey, userKeyEncString]); keyService.makeKeyPair.mockResolvedValue(userKeyPair); - accountApiService.registerFinish.mockResolvedValue(capchaBypassToken); + accountApiService.registerFinish.mockResolvedValue(); acceptOrgInviteService.getOrganizationInvite.mockResolvedValue(null); - const result = await service.finishRegistration( + await service.finishRegistration( email, passwordInputResult, undefined, @@ -357,16 +345,14 @@ describe("WebRegistrationFinishService", () => { emergencyAccessId, ); - expect(result).toEqual(capchaBypassToken); - expect(keyService.makeUserKey).toHaveBeenCalledWith(masterKey); expect(keyService.makeKeyPair).toHaveBeenCalledWith(userKey); expect(accountApiService.registerFinish).toHaveBeenCalledWith( expect.objectContaining({ email, emailVerificationToken: undefined, - masterPasswordHash: passwordInputResult.serverMasterKeyHash, - masterPasswordHint: passwordInputResult.hint, + masterPasswordHash: passwordInputResult.newServerMasterKeyHash, + masterPasswordHint: passwordInputResult.newPasswordHint, userSymmetricKey: userKeyEncString.encryptedString, userAsymmetricKeys: { publicKey: userKeyPair[0], @@ -387,13 +373,13 @@ describe("WebRegistrationFinishService", () => { ); }); - it("registers the user and returns a captcha bypass token when given a provider invite token", async () => { + it("registers the user when given a provider invite token", async () => { keyService.makeUserKey.mockResolvedValue([userKey, userKeyEncString]); keyService.makeKeyPair.mockResolvedValue(userKeyPair); - accountApiService.registerFinish.mockResolvedValue(capchaBypassToken); + accountApiService.registerFinish.mockResolvedValue(); acceptOrgInviteService.getOrganizationInvite.mockResolvedValue(null); - const result = await service.finishRegistration( + await service.finishRegistration( email, passwordInputResult, undefined, @@ -404,16 +390,14 @@ describe("WebRegistrationFinishService", () => { providerUserId, ); - expect(result).toEqual(capchaBypassToken); - expect(keyService.makeUserKey).toHaveBeenCalledWith(masterKey); expect(keyService.makeKeyPair).toHaveBeenCalledWith(userKey); expect(accountApiService.registerFinish).toHaveBeenCalledWith( expect.objectContaining({ email, emailVerificationToken: undefined, - masterPasswordHash: passwordInputResult.serverMasterKeyHash, - masterPasswordHint: passwordInputResult.hint, + masterPasswordHash: passwordInputResult.newServerMasterKeyHash, + masterPasswordHint: passwordInputResult.newPasswordHint, userSymmetricKey: userKeyEncString.encryptedString, userAsymmetricKeys: { publicKey: userKeyPair[0], 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..0a150b26ae2 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); } @@ -55,14 +58,17 @@ export class RotateableKeySetService { throw new Error("failed to rotate key set: newUserKey is required"); } - const publicKey = await this.encryptService.decryptToBytes( + const publicKey = await this.encryptService.unwrapEncapsulationKey( keySet.encryptedPublicKey, oldUserKey, ); 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/core/services/two-factor-auth/index.ts b/apps/web/src/app/auth/core/services/two-factor-auth/index.ts index ba2697fdee4..4ca57b34737 100644 --- a/apps/web/src/app/auth/core/services/two-factor-auth/index.ts +++ b/apps/web/src/app/auth/core/services/two-factor-auth/index.ts @@ -1,2 +1 @@ -export * from "./web-two-factor-auth-component.service"; export * from "./web-two-factor-auth-duo-component.service"; diff --git a/apps/web/src/app/auth/core/services/two-factor-auth/web-two-factor-auth-component.service.ts b/apps/web/src/app/auth/core/services/two-factor-auth/web-two-factor-auth-component.service.ts deleted file mode 100644 index 451cec57ddd..00000000000 --- a/apps/web/src/app/auth/core/services/two-factor-auth/web-two-factor-auth-component.service.ts +++ /dev/null @@ -1,14 +0,0 @@ -import { - DefaultTwoFactorAuthComponentService, - TwoFactorAuthComponentService, - LegacyKeyMigrationAction, -} from "@bitwarden/auth/angular"; - -export class WebTwoFactorAuthComponentService - extends DefaultTwoFactorAuthComponentService - implements TwoFactorAuthComponentService -{ - override determineLegacyKeyMigrationAction(): LegacyKeyMigrationAction { - return LegacyKeyMigrationAction.NAVIGATE_TO_MIGRATION_COMPONENT; - } -} diff --git a/apps/web/src/app/auth/emergency-access/accept/accept-emergency.component.ts b/apps/web/src/app/auth/emergency-access/accept/accept-emergency.component.ts index b3f635aee92..e1b7329504c 100644 --- a/apps/web/src/app/auth/emergency-access/accept/accept-emergency.component.ts +++ b/apps/web/src/app/auth/emergency-access/accept/accept-emergency.component.ts @@ -13,7 +13,6 @@ import { EmergencyAccessModule } from "../emergency-access.module"; import { EmergencyAccessService } from "../services/emergency-access.service"; @Component({ - standalone: true, imports: [SharedModule, EmergencyAccessModule], templateUrl: "accept-emergency.component.html", }) diff --git a/apps/web/src/app/auth/emergency-access/enums/emergency-access-status-type.ts b/apps/web/src/app/auth/emergency-access/enums/emergency-access-status-type.ts index 94400f34e6e..16aa2546101 100644 --- a/apps/web/src/app/auth/emergency-access/enums/emergency-access-status-type.ts +++ b/apps/web/src/app/auth/emergency-access/enums/emergency-access-status-type.ts @@ -1,3 +1,5 @@ +// FIXME: update to use a const object instead of a typescript enum +// eslint-disable-next-line @bitwarden/platform/no-enums export enum EmergencyAccessStatusType { Invited = 0, Accepted = 1, diff --git a/apps/web/src/app/auth/emergency-access/enums/emergency-access-type.ts b/apps/web/src/app/auth/emergency-access/enums/emergency-access-type.ts index 61a366c433e..ecb0c5a3d07 100644 --- a/apps/web/src/app/auth/emergency-access/enums/emergency-access-type.ts +++ b/apps/web/src/app/auth/emergency-access/enums/emergency-access-type.ts @@ -1,3 +1,5 @@ +// FIXME: update to use a const object instead of a typescript enum +// eslint-disable-next-line @bitwarden/platform/no-enums export enum EmergencyAccessType { View = 0, Takeover = 1, diff --git a/apps/web/src/app/auth/guards/deep-link.guard.ts b/apps/web/src/app/auth/guards/deep-link.guard.ts deleted file mode 100644 index 387e7b17e88..00000000000 --- a/apps/web/src/app/auth/guards/deep-link.guard.ts +++ /dev/null @@ -1,58 +0,0 @@ -// FIXME: Update this file to be type safe and remove this and next line -// @ts-strict-ignore -import { inject } from "@angular/core"; -import { CanActivateFn, Router } from "@angular/router"; - -import { AuthService } from "@bitwarden/common/auth/abstractions/auth.service"; -import { AuthenticationStatus } from "@bitwarden/common/auth/enums/authentication-status"; -import { Utils } from "@bitwarden/common/platform/misc/utils"; - -import { RouterService } from "../../core/router.service"; - -/** - * Guard to persist and apply deep links to handle users who are not unlocked. - * @returns returns true. If user is not Unlocked will store URL to state for redirect once - * user is unlocked/Authenticated. - */ -export function deepLinkGuard(): CanActivateFn { - return async (route, routerState) => { - // Inject Services - const authService = inject(AuthService); - const router = inject(Router); - const routerService = inject(RouterService); - - // Fetch State - const currentUrl = routerState.url; - const transientPreviousUrl = routerService.getPreviousUrl(); - const authStatus = await authService.getAuthStatus(); - - // Evaluate State - /** before anything else, check if the user is already unlocked. */ - if (authStatus === AuthenticationStatus.Unlocked) { - const persistedPreLoginUrl = await routerService.getAndClearLoginRedirectUrl(); - if (!Utils.isNullOrEmpty(persistedPreLoginUrl)) { - return router.navigateByUrl(persistedPreLoginUrl); - } - return true; - } - /** - * At this point the user is either `locked` or `loggedOut`, it doesn't matter. - * We opt to persist the currentUrl over the transient previousUrl. This supports - * the case where a user is locked out of their vault and they deep link from - * the "lock" page. - * - * When the user is locked out of their vault the currentUrl contains "lock" so it will - * not be persisted, the previousUrl will be persisted instead. - */ - if (isValidUrl(currentUrl)) { - await routerService.persistLoginRedirectUrl(currentUrl); - } else if (isValidUrl(transientPreviousUrl)) { - await routerService.persistLoginRedirectUrl(transientPreviousUrl); - } - return true; - }; - - function isValidUrl(url: string | null | undefined): boolean { - return !Utils.isNullOrEmpty(url) && !url?.toLocaleLowerCase().includes("/lock"); - } -} diff --git a/apps/web/src/app/auth/guards/deep-link.guard.spec.ts b/apps/web/src/app/auth/guards/deep-link/deep-link.guard.spec.ts similarity index 91% rename from apps/web/src/app/auth/guards/deep-link.guard.spec.ts rename to apps/web/src/app/auth/guards/deep-link/deep-link.guard.spec.ts index f9ced556e47..dba4dbd8357 100644 --- a/apps/web/src/app/auth/guards/deep-link.guard.spec.ts +++ b/apps/web/src/app/auth/guards/deep-link/deep-link.guard.spec.ts @@ -7,22 +7,25 @@ import { MockProxy, mock } from "jest-mock-extended"; import { AuthService } from "@bitwarden/common/auth/abstractions/auth.service"; import { AuthenticationStatus } from "@bitwarden/common/auth/enums/authentication-status"; -import { RouterService } from "../../core/router.service"; +import { RouterService } from "../../../core/router.service"; import { deepLinkGuard } from "./deep-link.guard"; @Component({ template: "", + standalone: false, }) export class GuardedRouteTestComponent {} @Component({ template: "", + standalone: false, }) export class LockTestComponent {} @Component({ template: "", + standalone: false, }) export class RedirectTestComponent {} @@ -96,6 +99,18 @@ describe("Deep Link Guard", () => { expect(routerService.persistLoginRedirectUrl).not.toHaveBeenCalled(); }); + it('should not persist routerService.previousUrl when routerService.previousUrl contains "login-initiated"', async () => { + // Arrange + authService.getAuthStatus.mockResolvedValue(AuthenticationStatus.Locked); + routerService.getPreviousUrl.mockReturnValue("/login-initiated"); + + // Act + await routerHarness.navigateByUrl("/lock-route"); + + // Assert + expect(routerService.persistLoginRedirectUrl).not.toHaveBeenCalled(); + }); + // Story: User's vault times out and previousUrl is undefined it("should not persist routerService.previousUrl when routerService.previousUrl is undefined", async () => { // Arrange diff --git a/apps/web/src/app/auth/guards/deep-link/deep-link.guard.ts b/apps/web/src/app/auth/guards/deep-link/deep-link.guard.ts new file mode 100644 index 00000000000..003f0580969 --- /dev/null +++ b/apps/web/src/app/auth/guards/deep-link/deep-link.guard.ts @@ -0,0 +1,99 @@ +import { inject } from "@angular/core"; +import { CanActivateFn, Router } from "@angular/router"; + +import { AuthService } from "@bitwarden/common/auth/abstractions/auth.service"; +import { AuthenticationStatus } from "@bitwarden/common/auth/enums/authentication-status"; +import { Utils } from "@bitwarden/common/platform/misc/utils"; + +import { RouterService } from "../../../core/router.service"; + +/** + * Guard to persist and apply deep links to handle users who are not unlocked. + * @returns returns true. If user is not Unlocked will store URL to state for redirect once + * user is unlocked/Authenticated. + */ +export function deepLinkGuard(): CanActivateFn { + return async (route, routerState) => { + // Inject Services + const authService = inject(AuthService); + const router = inject(Router); + const routerService = inject(RouterService); + + // Fetch State + const currentUrl = routerState.url; + const transientPreviousUrl = routerService.getPreviousUrl(); + const authStatus = await authService.getAuthStatus(); + + // Evaluate State + /** before anything else, check if the user is already unlocked. */ + if (authStatus === AuthenticationStatus.Unlocked) { + const persistedPreLoginUrl: string | undefined = + await routerService.getAndClearLoginRedirectUrl(); + if (persistedPreLoginUrl === undefined) { + // Url us undefined, so there is nothing to navigate to. + return true; + } + // Check if the url is empty or null + if (!Utils.isNullOrEmpty(persistedPreLoginUrl)) { + // const urlTree: string | UrlTree = persistedPreLoginUrl; + return router.navigateByUrl(persistedPreLoginUrl); + } + return true; + } + /** + * At this point the user is either `locked` or `loggedOut`, it doesn't matter. + * We opt to persist the currentUrl over the transient previousUrl. This supports + * the case where a user is locked out of their vault and they deep link from + * the "lock" page. + * + * When the user is locked out of their vault the currentUrl contains "lock" so it will + * not be persisted, the previousUrl will be persisted instead. + */ + if (isValidUrl(currentUrl)) { + await routerService.persistLoginRedirectUrl(currentUrl); + } else if (isValidUrl(transientPreviousUrl) && transientPreviousUrl !== undefined) { + await routerService.persistLoginRedirectUrl(transientPreviousUrl); + } + return true; + }; + + /** + * Check if the URL is valid for deep linking. A valid url is described as not including + * "lock" or "login-initiated". Valid urls are only urls that are not part of login or + * decryption flows. + * We ignore the "lock" url because standard SSO flows will send users to the lock component. + * We ignore "login-initiated" because TDE users decrypting with master passwords are + * sent to the lock component. + * @param url The URL to check. + * @returns True if the URL is valid, false otherwise. + */ + function isValidUrl(url: string | null | undefined): boolean { + if (url === undefined || url === null) { + return false; + } + + if (Utils.isNullOrEmpty(url)) { + return false; + } + const lowerCaseUrl: string = url.toLocaleLowerCase(); + + /** + * "Login-initiated" ignored because it is used for TDE users decrypting from a new device. A TDE user + * can opt to decrypt using their password. Decrypting with a password will send the user to the lock component, + * which is protected by the deep link guard. We don't persist the `login-initiated` url because it is not a + * valid deep-link. We don't want users to be sent to the login-initiated url when they are unlocked. + * If we did navigate to the login-initiated url, the user would get caught by the TDE Guard and be sent + * to the vault and not the intended deep link. + * + * "Lock" is ignored because users cannot deep-link to the lock component if they are already unlocked. + * Users logging in with SSO will be sent to the lock component after they are authenticated with their IdP. + * SSO users would be navigated to the "lock" component loop if we persisted the "lock" url. + */ + + if (lowerCaseUrl.includes("/login-initiated") || lowerCaseUrl.includes("/lock")) { + return false; + } + + return true; + } +} diff --git a/apps/web/src/app/auth/guards/deep-link/readme.md b/apps/web/src/app/auth/guards/deep-link/readme.md new file mode 100644 index 00000000000..82aebf95476 --- /dev/null +++ b/apps/web/src/app/auth/guards/deep-link/readme.md @@ -0,0 +1,23 @@ +# Deep-link Guard + +The `deep-link.guard.ts` supports users who are trying to access a protected route from an unauthenticated or locked state. + +This guard will persist the protected URL to session state when a user is either unauthenticated or in an encrypted/locked state. This allows users to have multiple tabs of the application running simultaneously without interfering with 'previousUrl` functionality. + +Writing to session state allows users who are authenticating through SSO to be routed to their identity provider and back without losing the protected route they were trying to access in the first place. + +The deep link guard will not persist Urls that are in the middle of authentication or decryption. SSO users will sometimes have to decrypt their vault after a successful authentication. This is why we do not persist the `/lock` route. + +## General operation + +The `deep-link.guard.ts` will always return true. The `deep-link.guard.ts` will only persist a URL if the user is in an unauthenticated or locked state. The URL cannot contain `/lock` or `/login-initiated`. The persisted URL is cleared from state when it is read. + +## Routes to protect + +The deep link guards should be used on routes where a user will be navigated to a protected route but may not be authenticated, decrypted, or have an account. + +A use cases is the `emergency-access` route which is a link that is sent to the user's email address, and in order for them to accept the request, they must first authenticate and decrypt. + +## TDE Users decrypting/unlocking with password + +For TDE users opting to decrypt with a password they will be routed from the `login-initiated` to the `lock` route. We ignore the `login-initiated` route for this reason allowing TDE users who decrypt/unlock with a password to still be navigated to the initial request. diff --git a/apps/web/src/app/auth/login/login-via-webauthn/login-via-webauthn.component.ts b/apps/web/src/app/auth/login/login-via-webauthn/login-via-webauthn.component.ts index 8f3a5ca3c37..d4a381159ab 100644 --- a/apps/web/src/app/auth/login/login-via-webauthn/login-via-webauthn.component.ts +++ b/apps/web/src/app/auth/login/login-via-webauthn/login-via-webauthn.component.ts @@ -7,6 +7,7 @@ import { CreatePasskeyIcon } from "@bitwarden/angular/auth/icons/create-passkey. @Component({ selector: "app-login-via-webauthn", templateUrl: "login-via-webauthn.component.html", + standalone: false, }) export class LoginViaWebAuthnComponent extends BaseLoginViaWebAuthnComponent { protected readonly Icons = { CreatePasskeyIcon, CreatePasskeyFailedIcon }; diff --git a/apps/web/src/app/auth/organization-invite/accept-organization.component.ts b/apps/web/src/app/auth/organization-invite/accept-organization.component.ts index 197b4031998..838a3029711 100644 --- a/apps/web/src/app/auth/organization-invite/accept-organization.component.ts +++ b/apps/web/src/app/auth/organization-invite/accept-organization.component.ts @@ -14,6 +14,7 @@ import { OrganizationInvite } from "./organization-invite"; @Component({ templateUrl: "accept-organization.component.html", + standalone: false, }) export class AcceptOrganizationComponent extends BaseAcceptComponent { orgName$ = this.acceptOrganizationInviteService.orgName$; 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..253328b0c04 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,7 +92,10 @@ describe("AcceptOrganizationInviteService", () => { "orgPublicKey", { encryptedString: "string" } as EncString, ]); - encryptService.encrypt.mockResolvedValue({ encryptedString: "string" } as EncString); + encryptService.wrapDecapsulationKey.mockResolvedValue({ + encryptedString: "string", + } as EncString); + encryptService.encryptString.mockResolvedValue({ encryptedString: "string" } as EncString); const invite = createOrgInvite({ initOrganization: true }); const result = await sut.validateAndAcceptInvite(invite); @@ -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..c68b174166d 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: @@ -147,7 +145,7 @@ export class AcceptOrganizationInviteService { const [encryptedOrgKey, orgKey] = await this.keyService.makeOrgKey(); const [orgPublicKey, encryptedOrgPrivateKey] = await this.keyService.makeKeyPair(orgKey); - const collection = await this.encryptService.encrypt( + const collection = await this.encryptService.encryptString( this.i18nService.t("defaultCollection"), orgKey, ); @@ -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/recover-delete.component.ts b/apps/web/src/app/auth/recover-delete.component.ts index 6b45421911d..7381d526879 100644 --- a/apps/web/src/app/auth/recover-delete.component.ts +++ b/apps/web/src/app/auth/recover-delete.component.ts @@ -13,6 +13,7 @@ import { ToastService } from "@bitwarden/components"; @Component({ selector: "app-recover-delete", templateUrl: "recover-delete.component.html", + standalone: false, }) export class RecoverDeleteComponent { protected recoverDeleteForm = new FormGroup({ diff --git a/apps/web/src/app/auth/recover-two-factor.component.spec.ts b/apps/web/src/app/auth/recover-two-factor.component.spec.ts index bf6d47e09e5..5977d994521 100644 --- a/apps/web/src/app/auth/recover-two-factor.component.spec.ts +++ b/apps/web/src/app/auth/recover-two-factor.component.spec.ts @@ -16,7 +16,6 @@ import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/pl import { ToastService } from "@bitwarden/components"; import { KeyService } from "@bitwarden/key-management"; import { I18nPipe } from "@bitwarden/ui-common"; -import { NewDeviceVerificationNoticeService } from "@bitwarden/vault"; import { RecoverTwoFactorComponent } from "./recover-two-factor.component"; @@ -35,7 +34,6 @@ describe("RecoverTwoFactorComponent", () => { let mockConfigService: MockProxy; let mockLoginSuccessHandlerService: MockProxy; let mockLogService: MockProxy; - let mockNewDeviceVerificationNoticeService: MockProxy; beforeEach(() => { mockRouter = mock(); @@ -48,7 +46,6 @@ describe("RecoverTwoFactorComponent", () => { mockConfigService = mock(); mockLoginSuccessHandlerService = mock(); mockLogService = mock(); - mockNewDeviceVerificationNoticeService = mock(); TestBed.configureTestingModule({ declarations: [RecoverTwoFactorComponent], @@ -63,10 +60,6 @@ describe("RecoverTwoFactorComponent", () => { { provide: ConfigService, useValue: mockConfigService }, { provide: LoginSuccessHandlerService, useValue: mockLoginSuccessHandlerService }, { provide: LogService, useValue: mockLogService }, - { - provide: NewDeviceVerificationNoticeService, - useValue: mockNewDeviceVerificationNoticeService, - }, ], imports: [I18nPipe], // FIXME(PM-18598): Replace unknownElements and unknownProperties with actual imports @@ -102,9 +95,6 @@ describe("RecoverTwoFactorComponent", () => { title: "", message: mockI18nService.t("youHaveBeenLoggedIn"), }); - expect( - mockNewDeviceVerificationNoticeService.updateNewDeviceVerificationSkipNoticeState, - ).toHaveBeenCalledWith(authResult.userId, true); expect(mockRouter.navigate).toHaveBeenCalledWith(["/settings/security/two-factor"]); }); diff --git a/apps/web/src/app/auth/recover-two-factor.component.ts b/apps/web/src/app/auth/recover-two-factor.component.ts index 35aa1aab7c1..a69da0a66bf 100644 --- a/apps/web/src/app/auth/recover-two-factor.component.ts +++ b/apps/web/src/app/auth/recover-two-factor.component.ts @@ -12,11 +12,11 @@ import { TokenTwoFactorRequest } from "@bitwarden/common/auth/models/request/ide import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; import { ToastService } from "@bitwarden/components"; -import { NewDeviceVerificationNoticeService } from "@bitwarden/vault"; @Component({ selector: "app-recover-two-factor", templateUrl: "recover-two-factor.component.html", + standalone: false, }) export class RecoverTwoFactorComponent implements OnInit { protected formGroup = new FormGroup({ @@ -37,7 +37,6 @@ export class RecoverTwoFactorComponent implements OnInit { private toastService: ToastService, private loginSuccessHandlerService: LoginSuccessHandlerService, private logService: LogService, - private newDeviceVerificationNoticeService: NewDeviceVerificationNoticeService, ) {} async ngOnInit() { @@ -82,12 +81,7 @@ export class RecoverTwoFactorComponent implements OnInit { remember: false, }; - const credentials = new PasswordLoginCredentials( - email, - this.masterPassword, - "", - twoFactorRequest, - ); + const credentials = new PasswordLoginCredentials(email, this.masterPassword, twoFactorRequest); try { const authResult = await this.loginStrategyService.logIn(credentials); @@ -104,13 +98,6 @@ export class RecoverTwoFactorComponent implements OnInit { await this.loginSuccessHandlerService.run(authResult.userId); - // Before routing, set the state to skip the new device notification. This is a temporary - // fix and will be cleaned up in PM-18485. - await this.newDeviceVerificationNoticeService.updateNewDeviceVerificationSkipNoticeState( - authResult.userId, - true, - ); - await this.router.navigate(["/settings/security/two-factor"]); } catch (error) { // If login errors, redirect to login page per product. Don't show error diff --git a/apps/web/src/app/auth/set-password.component.ts b/apps/web/src/app/auth/set-password.component.ts index ccd329dd640..e297426f2c1 100644 --- a/apps/web/src/app/auth/set-password.component.ts +++ b/apps/web/src/app/auth/set-password.component.ts @@ -11,6 +11,7 @@ import { AcceptOrganizationInviteService } from "./organization-invite/accept-or @Component({ selector: "app-set-password", templateUrl: "set-password.component.html", + standalone: false, }) export class SetPasswordComponent extends BaseSetPasswordComponent { routerService = inject(RouterService); diff --git a/apps/web/src/app/auth/settings/account/account.component.html b/apps/web/src/app/auth/settings/account/account.component.html index c5edc021614..74fa02f5f93 100644 --- a/apps/web/src/app/auth/settings/account/account.component.html +++ b/apps/web/src/app/auth/settings/account/account.component.html @@ -51,7 +51,4 @@ {{ "deleteAccount" | i18n }} - - - diff --git a/apps/web/src/app/auth/settings/account/account.component.ts b/apps/web/src/app/auth/settings/account/account.component.ts index 37bf535062f..921db19bc49 100644 --- a/apps/web/src/app/auth/settings/account/account.component.ts +++ b/apps/web/src/app/auth/settings/account/account.component.ts @@ -1,32 +1,33 @@ import { Component, OnInit, OnDestroy } from "@angular/core"; -import { - combineLatest, - firstValueFrom, - from, - lastValueFrom, - map, - Observable, - Subject, - takeUntil, -} from "rxjs"; +import { firstValueFrom, from, lastValueFrom, map, Observable, Subject, takeUntil } from "rxjs"; import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction"; import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; import { UserVerificationService } from "@bitwarden/common/auth/abstractions/user-verification/user-verification.service.abstraction"; 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 { DialogService } from "@bitwarden/components"; +import { HeaderModule } from "../../../layouts/header/header.module"; +import { SharedModule } from "../../../shared"; import { PurgeVaultComponent } from "../../../vault/settings/purge-vault.component"; +import { ChangeEmailComponent } from "./change-email.component"; +import { DangerZoneComponent } from "./danger-zone.component"; import { DeauthorizeSessionsComponent } from "./deauthorize-sessions.component"; import { DeleteAccountDialogComponent } from "./delete-account-dialog.component"; +import { ProfileComponent } from "./profile.component"; import { SetAccountVerifyDevicesDialogComponent } from "./set-account-verify-devices-dialog.component"; @Component({ - selector: "app-account", templateUrl: "account.component.html", + imports: [ + SharedModule, + HeaderModule, + ProfileComponent, + ChangeEmailComponent, + DangerZoneComponent, + ], }) export class AccountComponent implements OnInit, OnDestroy { private destroy$ = new Subject(); @@ -47,10 +48,6 @@ export class AccountComponent implements OnInit, OnDestroy { async ngOnInit() { const userId = await firstValueFrom(getUserId(this.accountService.activeAccount$)); - const isAccountDeprovisioningEnabled$ = this.configService.getFeatureFlag$( - FeatureFlag.AccountDeprovisioning, - ); - const userIsManagedByOrganization$ = this.organizationService .organizations$(userId) .pipe( @@ -61,25 +58,14 @@ export class AccountComponent implements OnInit, OnDestroy { this.showChangeEmail$ = hasMasterPassword$; - this.showPurgeVault$ = combineLatest([ - isAccountDeprovisioningEnabled$, - userIsManagedByOrganization$, - ]).pipe( - map( - ([isAccountDeprovisioningEnabled, userIsManagedByOrganization]) => - !isAccountDeprovisioningEnabled || !userIsManagedByOrganization, - ), + this.showPurgeVault$ = userIsManagedByOrganization$.pipe( + map((userIsManagedByOrganization) => !userIsManagedByOrganization), ); - this.showDeleteAccount$ = combineLatest([ - isAccountDeprovisioningEnabled$, - userIsManagedByOrganization$, - ]).pipe( - map( - ([isAccountDeprovisioningEnabled, userIsManagedByOrganization]) => - !isAccountDeprovisioningEnabled || !userIsManagedByOrganization, - ), + this.showDeleteAccount$ = userIsManagedByOrganization$.pipe( + map((userIsManagedByOrganization) => !userIsManagedByOrganization), ); + this.accountService.accountVerifyNewDeviceLogin$ .pipe(takeUntil(this.destroy$)) .subscribe((verifyDevices) => { diff --git a/apps/web/src/app/auth/settings/account/change-avatar-dialog.component.ts b/apps/web/src/app/auth/settings/account/change-avatar-dialog.component.ts index ba2a34c7143..6bb785fb8f5 100644 --- a/apps/web/src/app/auth/settings/account/change-avatar-dialog.component.ts +++ b/apps/web/src/app/auth/settings/account/change-avatar-dialog.component.ts @@ -24,6 +24,10 @@ import { ToastService, } from "@bitwarden/components"; +import { SharedModule } from "../../../shared"; + +import { SelectableAvatarComponent } from "./selectable-avatar.component"; + type ChangeAvatarDialogData = { profile: ProfileResponse; }; @@ -31,6 +35,7 @@ type ChangeAvatarDialogData = { @Component({ templateUrl: "change-avatar-dialog.component.html", encapsulation: ViewEncapsulation.None, + imports: [SharedModule, SelectableAvatarComponent], }) export class ChangeAvatarDialogComponent implements OnInit, OnDestroy { profile: ProfileResponse; diff --git a/apps/web/src/app/auth/settings/account/change-email.component.spec.ts b/apps/web/src/app/auth/settings/account/change-email.component.spec.ts index 838a50b5c2e..f5c0733e5b0 100644 --- a/apps/web/src/app/auth/settings/account/change-email.component.spec.ts +++ b/apps/web/src/app/auth/settings/account/change-email.component.spec.ts @@ -33,8 +33,7 @@ describe("ChangeEmailComponent", () => { accountService = mockAccountServiceWith("UserId" as UserId); await TestBed.configureTestingModule({ - declarations: [ChangeEmailComponent], - imports: [ReactiveFormsModule, SharedModule], + imports: [ReactiveFormsModule, SharedModule, ChangeEmailComponent], providers: [ { provide: AccountService, useValue: accountService }, { provide: ApiService, useValue: apiService }, diff --git a/apps/web/src/app/auth/settings/account/change-email.component.ts b/apps/web/src/app/auth/settings/account/change-email.component.ts index caf7e0933b0..a55846a5c0f 100644 --- a/apps/web/src/app/auth/settings/account/change-email.component.ts +++ b/apps/web/src/app/auth/settings/account/change-email.component.ts @@ -14,9 +14,12 @@ import { UserId } from "@bitwarden/common/types/guid"; import { ToastService } from "@bitwarden/components"; import { KdfConfigService, KeyService } from "@bitwarden/key-management"; +import { SharedModule } from "../../../shared"; + @Component({ selector: "app-change-email", templateUrl: "change-email.component.html", + imports: [SharedModule], }) export class ChangeEmailComponent implements OnInit { tokenSent = false; diff --git a/apps/web/src/app/auth/settings/account/danger-zone.component.ts b/apps/web/src/app/auth/settings/account/danger-zone.component.ts index 1abea314b50..05fd22d087d 100644 --- a/apps/web/src/app/auth/settings/account/danger-zone.component.ts +++ b/apps/web/src/app/auth/settings/account/danger-zone.component.ts @@ -1,13 +1,10 @@ // FIXME: Update this file to be type safe and remove this and next line // @ts-strict-ignore import { CommonModule } from "@angular/common"; -import { Component, OnInit } from "@angular/core"; -import { Observable } from "rxjs"; +import { Component } from "@angular/core"; -import { JslibModule } from "@bitwarden/angular/jslib.module"; -import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum"; -import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; import { TypographyModule } from "@bitwarden/components"; +import { I18nPipe } from "@bitwarden/ui-common"; /** * Component for the Danger Zone section of the Account/Organization Settings page. @@ -15,16 +12,6 @@ import { TypographyModule } from "@bitwarden/components"; @Component({ selector: "app-danger-zone", templateUrl: "danger-zone.component.html", - standalone: true, - imports: [TypographyModule, JslibModule, CommonModule], + imports: [CommonModule, TypographyModule, I18nPipe], }) -export class DangerZoneComponent implements OnInit { - constructor(private configService: ConfigService) {} - accountDeprovisioningEnabled$: Observable; - - ngOnInit(): void { - this.accountDeprovisioningEnabled$ = this.configService.getFeatureFlag$( - FeatureFlag.AccountDeprovisioning, - ); - } -} +export class DangerZoneComponent {} diff --git a/apps/web/src/app/auth/settings/account/deauthorize-sessions.component.ts b/apps/web/src/app/auth/settings/account/deauthorize-sessions.component.ts index a7c466d4ffc..f75320e8335 100644 --- a/apps/web/src/app/auth/settings/account/deauthorize-sessions.component.ts +++ b/apps/web/src/app/auth/settings/account/deauthorize-sessions.component.ts @@ -1,6 +1,7 @@ import { Component } from "@angular/core"; import { FormBuilder } from "@angular/forms"; +import { UserVerificationFormInputComponent } from "@bitwarden/auth/angular"; import { ApiService } from "@bitwarden/common/abstractions/api.service"; import { UserVerificationService } from "@bitwarden/common/auth/abstractions/user-verification/user-verification.service.abstraction"; import { Verification } from "@bitwarden/common/auth/types/verification"; @@ -9,9 +10,11 @@ import { LogService } from "@bitwarden/common/platform/abstractions/log.service" import { MessagingService } from "@bitwarden/common/platform/abstractions/messaging.service"; import { DialogService, ToastService } from "@bitwarden/components"; +import { SharedModule } from "../../../shared"; + @Component({ - selector: "app-deauthorize-sessions", templateUrl: "deauthorize-sessions.component.html", + imports: [SharedModule, UserVerificationFormInputComponent], }) export class DeauthorizeSessionsComponent { deauthForm = this.formBuilder.group({ diff --git a/apps/web/src/app/auth/settings/account/delete-account-dialog.component.ts b/apps/web/src/app/auth/settings/account/delete-account-dialog.component.ts index 07e6052e0a1..7e8f169994f 100644 --- a/apps/web/src/app/auth/settings/account/delete-account-dialog.component.ts +++ b/apps/web/src/app/auth/settings/account/delete-account-dialog.component.ts @@ -3,14 +3,18 @@ import { Component } from "@angular/core"; import { FormBuilder } from "@angular/forms"; +import { UserVerificationFormInputComponent } from "@bitwarden/auth/angular"; import { AccountApiService } from "@bitwarden/common/auth/abstractions/account-api.service"; import { Verification } from "@bitwarden/common/auth/types/verification"; import { ErrorResponse } from "@bitwarden/common/models/response/error.response"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { DialogRef, DialogService, ToastService } from "@bitwarden/components"; +import { SharedModule } from "../../../shared"; + @Component({ templateUrl: "delete-account-dialog.component.html", + imports: [SharedModule, UserVerificationFormInputComponent], }) export class DeleteAccountDialogComponent { deleteForm = this.formBuilder.group({ diff --git a/apps/web/src/app/auth/settings/account/profile.component.html b/apps/web/src/app/auth/settings/account/profile.component.html index d2a887c9b98..74e9cf08f89 100644 --- a/apps/web/src/app/auth/settings/account/profile.component.html +++ b/apps/web/src/app/auth/settings/account/profile.component.html @@ -38,7 +38,11 @@
{{ "accountIsOwnedMessage" | i18n: managingOrganization?.name }} - +
diff --git a/apps/web/src/app/auth/settings/account/profile.component.ts b/apps/web/src/app/auth/settings/account/profile.component.ts index 72731363806..a0572b846db 100644 --- a/apps/web/src/app/auth/settings/account/profile.component.ts +++ b/apps/web/src/app/auth/settings/account/profile.component.ts @@ -2,7 +2,7 @@ // @ts-strict-ignore import { Component, OnDestroy, OnInit } from "@angular/core"; import { FormControl, FormGroup } from "@angular/forms"; -import { firstValueFrom, map, Observable, of, Subject, switchMap, takeUntil } from "rxjs"; +import { firstValueFrom, map, Observable, Subject, takeUntil } from "rxjs"; import { ApiService } from "@bitwarden/common/abstractions/api.service"; import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction"; @@ -10,17 +10,20 @@ import { Organization } from "@bitwarden/common/admin-console/models/domain/orga import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; import { UpdateProfileRequest } from "@bitwarden/common/auth/models/request/update-profile.request"; import { getUserId } from "@bitwarden/common/auth/services/account.service"; -import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum"; import { ProfileResponse } from "@bitwarden/common/models/response/profile.response"; -import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { DialogService, ToastService } from "@bitwarden/components"; +import { DynamicAvatarComponent } from "../../../components/dynamic-avatar.component"; +import { SharedModule } from "../../../shared"; +import { AccountFingerprintComponent } from "../../../shared/components/account-fingerprint/account-fingerprint.component"; + import { ChangeAvatarDialogComponent } from "./change-avatar-dialog.component"; @Component({ selector: "app-profile", templateUrl: "profile.component.html", + imports: [SharedModule, DynamicAvatarComponent, AccountFingerprintComponent], }) export class ProfileComponent implements OnInit, OnDestroy { loading = true; @@ -40,7 +43,6 @@ export class ProfileComponent implements OnInit, OnDestroy { private accountService: AccountService, private dialogService: DialogService, private toastService: ToastService, - private configService: ConfigService, private organizationService: OrganizationService, ) {} @@ -53,21 +55,12 @@ export class ProfileComponent implements OnInit, OnDestroy { const userId = await firstValueFrom(getUserId(this.accountService.activeAccount$)); - this.managingOrganization$ = this.configService - .getFeatureFlag$(FeatureFlag.AccountDeprovisioning) + this.managingOrganization$ = this.organizationService + .organizations$(userId) .pipe( - switchMap((isAccountDeprovisioningEnabled) => - isAccountDeprovisioningEnabled - ? this.organizationService - .organizations$(userId) - .pipe( - map((organizations) => - organizations.find((o) => o.userIsManagedByOrganization === true), - ), - ) - : of(null), - ), + map((organizations) => organizations.find((o) => o.userIsManagedByOrganization === true)), ); + this.formGroup.get("name").setValue(this.profile.name); this.formGroup.get("email").setValue(this.profile.email); diff --git a/apps/web/src/app/components/selectable-avatar.component.ts b/apps/web/src/app/auth/settings/account/selectable-avatar.component.ts similarity index 91% rename from apps/web/src/app/components/selectable-avatar.component.ts rename to apps/web/src/app/auth/settings/account/selectable-avatar.component.ts index 7a746481563..630c0e949ad 100644 --- a/apps/web/src/app/components/selectable-avatar.component.ts +++ b/apps/web/src/app/auth/settings/account/selectable-avatar.component.ts @@ -1,7 +1,10 @@ // FIXME: Update this file to be type safe and remove this and next line // @ts-strict-ignore +import { NgClass } from "@angular/common"; import { Component, EventEmitter, Input, Output } from "@angular/core"; +import { AvatarModule } from "@bitwarden/components"; + @Component({ selector: "selectable-avatar", template: ` `, + imports: [NgClass, AvatarModule], }) export class SelectableAvatarComponent { @Input() id: string; diff --git a/apps/web/src/app/auth/settings/account/set-account-verify-devices-dialog.component.ts b/apps/web/src/app/auth/settings/account/set-account-verify-devices-dialog.component.ts index 69240426249..63a26f08eee 100644 --- a/apps/web/src/app/auth/settings/account/set-account-verify-devices-dialog.component.ts +++ b/apps/web/src/app/auth/settings/account/set-account-verify-devices-dialog.component.ts @@ -29,7 +29,6 @@ import { @Component({ templateUrl: "./set-account-verify-devices-dialog.component.html", - standalone: true, imports: [ CommonModule, ReactiveFormsModule, 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..15d106057ba 100644 --- a/apps/web/src/app/auth/settings/change-password.component.ts +++ b/apps/web/src/app/auth/settings/change-password.component.ts @@ -12,16 +12,10 @@ import { MasterPasswordApiService } from "@bitwarden/common/auth/abstractions/ma import { UserVerificationService } from "@bitwarden/common/auth/abstractions/user-verification/user-verification.service.abstraction"; import { PasswordRequest } from "@bitwarden/common/auth/models/request/password.request"; import { getUserId } from "@bitwarden/common/auth/services/account.service"; -import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum"; import { InternalMasterPasswordServiceAbstraction } from "@bitwarden/common/key-management/master-password/abstractions/master-password.service.abstraction"; -import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { MessagingService } from "@bitwarden/common/platform/abstractions/messaging.service"; import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; -import { HashPurpose } from "@bitwarden/common/platform/enums"; -import { EncString } from "@bitwarden/common/platform/models/domain/enc-string"; -import { UserId } from "@bitwarden/common/types/guid"; -import { MasterKey, UserKey } from "@bitwarden/common/types/key"; import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service"; import { SyncService } from "@bitwarden/common/vault/abstractions/sync/sync.service.abstraction"; import { DialogService, ToastService } from "@bitwarden/components"; @@ -29,9 +23,13 @@ import { KdfConfigService, KeyService } from "@bitwarden/key-management"; import { UserKeyRotationService } from "../../key-management/key-rotation/user-key-rotation.service"; +/** + * @deprecated use the auth `PasswordSettingsComponent` instead + */ @Component({ selector: "app-change-password", templateUrl: "change-password.component.html", + standalone: false, }) export class ChangePasswordComponent extends BaseChangePasswordComponent @@ -43,7 +41,6 @@ export class ChangePasswordComponent masterPasswordHint: string; checkForBreaches = true; characterMinimumMessage = ""; - userkeyRotationV2 = false; constructor( i18nService: I18nService, @@ -63,7 +60,6 @@ export class ChangePasswordComponent protected masterPasswordService: InternalMasterPasswordServiceAbstraction, accountService: AccountService, toastService: ToastService, - private configService: ConfigService, ) { super( i18nService, @@ -80,8 +76,6 @@ export class ChangePasswordComponent } async ngOnInit() { - this.userkeyRotationV2 = await this.configService.getFeatureFlag(FeatureFlag.UserKeyRotationV2); - if (!(await this.userVerificationService.hasMasterPassword())) { // FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling. // eslint-disable-next-line @typescript-eslint/no-floating-promises @@ -131,7 +125,7 @@ export class ChangePasswordComponent content: this.i18nService.t("updateEncryptionKeyWarning") + " " + - this.i18nService.t("updateEncryptionKeyExportWarning") + + this.i18nService.t("updateEncryptionKeyAccountExportWarning") + " " + this.i18nService.t("rotateEncKeyConfirmation"), type: "warning", @@ -144,22 +138,14 @@ export class ChangePasswordComponent } async submit() { - if (this.userkeyRotationV2) { - this.loading = true; - await this.submitNew(); - this.loading = false; - } else { - await this.submitOld(); - } - } - - async submitNew() { + this.loading = true; if (this.currentMasterPassword == null || this.currentMasterPassword === "") { this.toastService.showToast({ variant: "error", title: this.i18nService.t("errorOccurred"), message: this.i18nService.t("masterPasswordRequired"), }); + this.loading = false; return; } @@ -172,6 +158,7 @@ export class ChangePasswordComponent title: this.i18nService.t("errorOccurred"), message: this.i18nService.t("hintEqualsPassword"), }); + this.loading = false; return; } @@ -181,6 +168,7 @@ export class ChangePasswordComponent } if (!(await this.strongPassword())) { + this.loading = false; return; } @@ -203,6 +191,8 @@ export class ChangePasswordComponent title: this.i18nService.t("errorOccurred"), message: e.message, }); + } finally { + this.loading = false; } } @@ -266,113 +256,4 @@ export class ChangePasswordComponent }); } } - - async submitOld() { - if ( - this.masterPasswordHint != null && - this.masterPasswordHint.toLowerCase() === this.masterPassword.toLowerCase() - ) { - this.toastService.showToast({ - variant: "error", - title: this.i18nService.t("errorOccurred"), - message: this.i18nService.t("hintEqualsPassword"), - }); - return; - } - - this.leakedPassword = false; - if (this.checkForBreaches) { - this.leakedPassword = (await this.auditService.passwordLeaked(this.masterPassword)) > 0; - } - - await super.submit(); - } - - async setupSubmitActions() { - if (this.currentMasterPassword == null || this.currentMasterPassword === "") { - this.toastService.showToast({ - variant: "error", - title: this.i18nService.t("errorOccurred"), - message: this.i18nService.t("masterPasswordRequired"), - }); - return false; - } - - if (this.rotateUserKey) { - await this.syncService.fullSync(true); - } - - return super.setupSubmitActions(); - } - - async performSubmitActions( - newMasterPasswordHash: string, - 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 = await firstValueFrom(getUserId(this.accountService.activeAccount$)); - const newLocalKeyHash = await this.keyService.hashMasterKey( - this.masterPassword, - newMasterKey, - HashPurpose.LocalAuthorization, - ); - - const userKey = await this.masterPasswordService.decryptUserKeyWithMasterKey(masterKey, userId); - if (userKey == null) { - this.toastService.showToast({ - variant: "error", - title: null, - message: this.i18nService.t("invalidMasterPassword"), - }); - return; - } - - const request = new PasswordRequest(); - request.masterPasswordHash = await this.keyService.hashMasterKey( - this.currentMasterPassword, - masterKey, - ); - request.masterPasswordHint = this.masterPasswordHint; - request.newMasterPasswordHash = newMasterPasswordHash; - request.key = newUserKey[1].encryptedString; - - try { - if (this.rotateUserKey) { - this.formPromise = this.masterPasswordApiService.postPassword(request).then(async () => { - // we need to save this for local masterkey verification during rotation - await this.masterPasswordService.setMasterKeyHash(newLocalKeyHash, userId as UserId); - await this.masterPasswordService.setMasterKey(newMasterKey, userId as UserId); - return this.updateKey(); - }); - } else { - this.formPromise = this.masterPasswordApiService.postPassword(request); - } - - await this.formPromise; - - this.toastService.showToast({ - variant: "success", - title: this.i18nService.t("masterPasswordChanged"), - message: this.i18nService.t("logBackIn"), - }); - this.messagingService.send("logout"); - } catch { - this.toastService.showToast({ - variant: "error", - title: null, - message: this.i18nService.t("errorOccurred"), - }); - } - } - - private async updateKey() { - const user = await firstValueFrom(this.accountService.activeAccount$); - await this.keyRotationService.rotateUserKeyAndEncryptedDataLegacy(this.masterPassword, user); - } } diff --git a/apps/web/src/app/auth/settings/emergency-access/confirm/emergency-access-confirm.component.ts b/apps/web/src/app/auth/settings/emergency-access/confirm/emergency-access-confirm.component.ts index f3fd19a4e8b..cd7a585f3b1 100644 --- a/apps/web/src/app/auth/settings/emergency-access/confirm/emergency-access-confirm.component.ts +++ b/apps/web/src/app/auth/settings/emergency-access/confirm/emergency-access-confirm.component.ts @@ -8,6 +8,8 @@ import { LogService } from "@bitwarden/common/platform/abstractions/log.service" import { DialogConfig, DialogRef, DIALOG_DATA, DialogService } from "@bitwarden/components"; import { KeyService } from "@bitwarden/key-management"; +// FIXME: update to use a const object instead of a typescript enum +// eslint-disable-next-line @bitwarden/platform/no-enums export enum EmergencyAccessConfirmDialogResult { Confirmed = "confirmed", } @@ -24,6 +26,7 @@ type EmergencyAccessConfirmDialogData = { @Component({ selector: "emergency-access-confirm", templateUrl: "emergency-access-confirm.component.html", + standalone: false, }) export class EmergencyAccessConfirmComponent implements OnInit { loading = true; diff --git a/apps/web/src/app/auth/settings/emergency-access/emergency-access-add-edit.component.ts b/apps/web/src/app/auth/settings/emergency-access/emergency-access-add-edit.component.ts index cf52969c244..2f3f3a20b04 100644 --- a/apps/web/src/app/auth/settings/emergency-access/emergency-access-add-edit.component.ts +++ b/apps/web/src/app/auth/settings/emergency-access/emergency-access-add-edit.component.ts @@ -26,6 +26,8 @@ export type EmergencyAccessAddEditDialogData = { readOnly: boolean; }; +// FIXME: update to use a const object instead of a typescript enum +// eslint-disable-next-line @bitwarden/platform/no-enums export enum EmergencyAccessAddEditDialogResult { Saved = "saved", Canceled = "canceled", @@ -34,6 +36,7 @@ export enum EmergencyAccessAddEditDialogResult { @Component({ selector: "emergency-access-add-edit", templateUrl: "emergency-access-add-edit.component.html", + standalone: false, }) export class EmergencyAccessAddEditComponent implements OnInit { loading = true; diff --git a/apps/web/src/app/auth/settings/emergency-access/emergency-access.component.html b/apps/web/src/app/auth/settings/emergency-access/emergency-access.component.html index ab93f0be3bc..8a802e4f6af 100644 --- a/apps/web/src/app/auth/settings/emergency-access/emergency-access.component.html +++ b/apps/web/src/app/auth/settings/emergency-access/emergency-access.component.html @@ -272,7 +272,3 @@ - - - - diff --git a/apps/web/src/app/auth/settings/emergency-access/emergency-access.component.ts b/apps/web/src/app/auth/settings/emergency-access/emergency-access.component.ts index dc464c18059..23bf0c22bc7 100644 --- a/apps/web/src/app/auth/settings/emergency-access/emergency-access.component.ts +++ b/apps/web/src/app/auth/settings/emergency-access/emergency-access.component.ts @@ -1,6 +1,6 @@ // FIXME: Update this file to be type safe and remove this and next line // @ts-strict-ignore -import { Component, OnInit, ViewChild, ViewContainerRef } from "@angular/core"; +import { Component, OnInit } from "@angular/core"; import { lastValueFrom, Observable, firstValueFrom, switchMap } from "rxjs"; import { UserNamePipe } from "@bitwarden/angular/pipes/user-name.pipe"; @@ -42,14 +42,9 @@ import { @Component({ selector: "emergency-access", templateUrl: "emergency-access.component.html", + standalone: false, }) export class EmergencyAccessComponent implements OnInit { - @ViewChild("addEdit", { read: ViewContainerRef, static: true }) addEditModalRef: ViewContainerRef; - @ViewChild("takeoverTemplate", { read: ViewContainerRef, static: true }) - takeoverModalRef: ViewContainerRef; - @ViewChild("confirmTemplate", { read: ViewContainerRef, static: true }) - confirmModalRef: ViewContainerRef; - loaded = false; canAccessPremium$: Observable; trustedContacts: GranteeEmergencyAccess[]; diff --git a/apps/web/src/app/auth/settings/emergency-access/takeover/emergency-access-takeover.component.ts b/apps/web/src/app/auth/settings/emergency-access/takeover/emergency-access-takeover.component.ts index c80f82ae126..d683545db59 100644 --- a/apps/web/src/app/auth/settings/emergency-access/takeover/emergency-access-takeover.component.ts +++ b/apps/web/src/app/auth/settings/emergency-access/takeover/emergency-access-takeover.component.ts @@ -24,6 +24,8 @@ import { KdfType, KdfConfigService, KeyService } from "@bitwarden/key-management import { EmergencyAccessService } from "../../../emergency-access"; +// FIXME: update to use a const object instead of a typescript enum +// eslint-disable-next-line @bitwarden/platform/no-enums export enum EmergencyAccessTakeoverResultType { Done = "done", } @@ -38,6 +40,7 @@ type EmergencyAccessTakeoverDialogData = { @Component({ selector: "emergency-access-takeover", templateUrl: "emergency-access-takeover.component.html", + standalone: false, }) export class EmergencyAccessTakeoverComponent extends ChangePasswordComponent diff --git a/apps/web/src/app/auth/settings/emergency-access/view/emergency-access-view.component.html b/apps/web/src/app/auth/settings/emergency-access/view/emergency-access-view.component.html index 87e2643c178..20cc50c4d59 100644 --- a/apps/web/src/app/auth/settings/emergency-access/view/emergency-access-view.component.html +++ b/apps/web/src/app/auth/settings/emergency-access/view/emergency-access-view.component.html @@ -19,7 +19,7 @@ >